web-dev-qa-db-ja.com

Clean Architecture / Onion Architectureで意味のある型にプリミティブ型を変換する場所

本「ドメイン駆動設計の実装」(361ページ)は、特別なタイプを使用していくつかの種類のIDを区別することを推奨しています。 g。タイプIntまたはLongの_1_の代わりにBookId(1)を使用します。私のClean ArchitectureまたはOnion Architectureでは、最外層はHTTPアダプタですタイプidLongを取ります。次に、このアダプターはアプリケーションサービスを呼び出し、次にアプリケーションサービスがドメイン(この場合はリポジトリー)を呼び出します。フローは次のとおりです。

_HTTP Adapter → Application Service → Domain
_

問題は、LongBookIdに変換する場所です。原則として、2つのオプションがあります。

1) HTTPアダプターで変換します。これには、生の型が存在しなくなり、AuthorIdを使用する必要があったBookIdを誰も使用できなくなるという利点があります。会話はフレームワークで実行することもできるため、コードで未加工の型を変換または処理する必要はありません。 Jersey(JAX/RS)の例は次のようになります。

_@GET
@Path("/books/{id}")
fun findById(id: BookId): Response { ... }
_

フレームワークによって登録されたコンバーターは、リクエストからLong値を取得し、自動的にBookIdに変換します。

_HTTP Adapter  → Application Service → Domain
Long → BookId   BookID                BookId
_

2)Long値をアプリケーションサービスに渡すことができます。ただし、ここでは何のメリットもありません。

_HTTP Adapter → Application Service → Domain
Long           Long → BookId         BookID
_

質問:変換に最適な場所はどこですか?できるだけ早く値を変換しない理由はありますか?

5
deamon

私は最近、このタイプの変換をHTTPサービスを構築するためのフレームワークに正確に実装しました。私はプロジェクトコードがBookIdスタイルタイプのみを参照するように、フレームワークの境界で実装することを選択しました。利点の1つ(およびこの方法で行う動機)の1つは、関数を非プリミティブ型で定義すると、特定の種類の面倒な検証を自動化して、意味のあるエラーを自動的に返すことができることです。たとえば、私のフレームワークは、署名の「意味のあるタイプ」の不変条件を満たさないリクエストデータに対して、HTTP 400または422を自動的に返します。

次に例を示します(フレームワークはF#用ですが、概念は普遍的です)。

[<HttpGet("[version]/invoices/{invoiceNumber}")>]
let getInvoice (invoiceNumber: InvoiceNumber) =
    api {
        let! repository = inject<IInvoiceRepository>()
        let! invoice = repository.GetInvoiceByInvoiceNumber(invoiceNumber)
        return
            match invoice with
            | Some i -> Http.ok(i)
            | None -> Http.notFound()
    } 

したがって、この例では、InvoiceNumberが意味のあるプリミティブ型の1つであり、10桁の負でない整数として定義されているとしましょう。検証を行ういくつかの変換関数を定義すると、フレームワークは自動的にHTTPリクエストを分解し、変換関数を使用してリクエストデータの検証を試みます。失敗した場合、フレームワークは関数を呼び出すことすらなく、失敗の詳細を含む検証エラーを自動的に返します。それが機能する場合、フレームワークは関数を呼び出し、基本となるプリミティブを見ることはありません。

/// Must be a 10-digit numerical string
[<Struct; Validated>] type InvoiceNumber = private InvoiceNumber of string

module InvoiceNumber =
    let create = 
        validation<InvoiceNumber> {
            rule Rules.isNumerical InvoiceNumberMustBeNumerical
            rule (Rules.isLength 10) InvoiceNumberMustBeTenDigits
            rule Rules.isPositive InvoiceNumberMustBePositive
        }

    let value (InvoiceNumber invoice) = invoice

このすべてがフレームワークでどのように処理されるかについて知りたい場合は、 GitHub repo をご覧ください。ドキュメントはまだ完成していませんが、コードは利用できます。

2

A)bookIdが存在することを知らないコードベースの場所があります。

B)bookIdが存在することを知っているコードベースの場所があります。

C)bookIdが本当にlongであることを知っているコードベースの場所があります。

最大化A.最小化B.最小化C.

知らなくても、知らなくてもいいのです。

2
candied_orange

私は多くの人がこの種の「プリミティブを避けさせる」タイプに悩まされているのではないかと思います。あなたはその理由の1つに触れたかもしれません。

最も外側のレイヤーで変換するのは明らかな選択のようです。あなたが言うように、フレームワークはそのようなことを処理できなければなりません。

ただし、実際には、動作しないときにデバッグするバグがあるため、選択した言語でプリミティブを対応するもの以外に変換する場合は、特別な変換クラスを設定する必要がある場合があります。

あなたのフレームワークが文句なしにそれを処理するなら、素晴らしいです。厄介な場合は、コントローラのサービス前の呼び出しでそれを行います。結局のところ、すべてのドメインコードで特殊な型を利用し、プリミティブの変換について心配する必要はありません。

0
Ewan