本「ドメイン駆動設計の実装」(361ページ)は、特別なタイプを使用していくつかの種類のIDを区別することを推奨しています。 g。タイプInt
またはLong
の_1
_の代わりにBookId(1)
を使用します。私のClean ArchitectureまたはOnion Architectureでは、最外層はHTTPアダプタですタイプid
のLong
を取ります。次に、このアダプターはアプリケーションサービスを呼び出し、次にアプリケーションサービスがドメイン(この場合はリポジトリー)を呼び出します。フローは次のとおりです。
_HTTP Adapter → Application Service → Domain
_
問題は、Long
をBookId
に変換する場所です。原則として、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
_
質問:変換に最適な場所はどこですか?できるだけ早く値を変換しない理由はありますか?
私は最近、このタイプの変換を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 をご覧ください。ドキュメントはまだ完成していませんが、コードは利用できます。
A)bookId
が存在することを知らないコードベースの場所があります。
B)bookId
が存在することを知っているコードベースの場所があります。
C)bookId
が本当にlong
であることを知っているコードベースの場所があります。
最大化A.最小化B.最小化C.
知らなくても、知らなくてもいいのです。
私は多くの人がこの種の「プリミティブを避けさせる」タイプに悩まされているのではないかと思います。あなたはその理由の1つに触れたかもしれません。
最も外側のレイヤーで変換するのは明らかな選択のようです。あなたが言うように、フレームワークはそのようなことを処理できなければなりません。
ただし、実際には、動作しないときにデバッグするバグがあるため、選択した言語でプリミティブを対応するもの以外に変換する場合は、特別な変換クラスを設定する必要がある場合があります。
あなたのフレームワークが文句なしにそれを処理するなら、素晴らしいです。厄介な場合は、コントローラのサービス前の呼び出しでそれを行います。結局のところ、すべてのドメインコードで特殊な型を利用し、プリミティブの変換について心配する必要はありません。