コンテンツにスキップ
検索語を入力してください

    ライブラリガイド

    Cats、Circe、doobieなどはKamaeのドメイン規約を補助するライブラリである。トピック別リファレンス(エラーハンドリング、境界防御など)と矛盾する場合は、そちらを優先する。

    ここでは「よくある組み合わせ」とデフォルトの置き場所をまとめる。個別の設計判断は エラーハンドリング境界防御ドメインモデリングPII 保護 を参照する。

    用途ガイド付きライブラリ検出のみ(ローカル慣習の参考)
    エフェクトcats-corecats-effectziomonixscalaz
    JSONcircePlay JSON、jsoniter-scala
    設定pureconfigcaliban config、Typesafe Config 直読み
    SQL / ORMdoobieslickQuill、skunk
    ストリームfs2Akka Streams、Pekko Streams
    検証 / newtyperefinednewtype、手書き opaque type
    PII / シークレットopaque credential wrapper(本ガイド secretsvault 連携、環境変数直読み
    テストscalacheckmunitScalaTestspecs2

    cats または cats-effect があるとき:

    • ユースケースtraitに MonadFunctorApplicativeError 制約を適切に使う
    • 十分な理由がなければドメイン遷移を F[_] から解放する
    • アダプター境界で attempt / handleErrorWith によりエラーをマップする
    • I/Oには IO / F の遅延を優先し、flatMap 内で blocking なしにブロックしない

    エラーチャネルでは、純粋ドメインコードの Either と、アプリケーションコードの F[Either[E, A]] または ApplicativeError[F, E, *] は、一貫して使えばどちらも許容される。

    スタックパターントピックガイド
    cats-effect + ポートリポジトリ trait は F[_]、実装は IOアプリケーション配線
    ApplicativeError + ユースケースビジネス失敗を型付きエラーで表現エラーハンドリング
    Either + ドメイン遷移は純粋 Either、ユースケースが fromEither状態遷移

    ZIOがあるとき:

    • ユースケースを ZIO[Env, UseCaseError, A] でモデルする
    • ドメイン遷移は純粋に保ち、ZIO.fromEither で呼ぶ
    • レイヤーはcomposition rootのみで提供する
    • ビジネス失敗には Throwable ではなく型付きエラーをエラーチャネルに使う

    ドメインパッケージは、プロジェクトがエフェクト型をアプリケーションコードと明示的に同居させない限り zio に依存しない。

    スタックパターントピックガイド
    ZLayer + ポートアダプター実装のみレイヤー化アプリケーション配線
    ZIO + Either 遷移fromEither でドメインを呼ぶ状態遷移

    CirceはJSON境界向けであり、ドメイン不変条件の権威にはしない。

    import io.circe.Decoder
    final case class RequestDto(requestId: String, passengerId: String, status: String)
    object RequestDto:
    given Decoder[RequestDto] = Decoder.derived

    Decoder.derived はビジネスルールを検証しない。ネストしたフィールドのcodecも、implicit scopeに Decoder がない限り自動導出されない。

    ドメイン型にはバリデータを使う

    Section titled “ドメイン型にはバリデータを使う”

    DTOにデコードし、明示的な Either マッピングで変換する。検証がdecoderに埋め込まれテストされている場合を除き、不変条件を持つ型に Decoder[WaitingRequest] を避ける。

    def decodeWaiting(dto: RequestDto): Either[BoundaryError, WaitingRequest] =
    for
    requestId <- RequestId(dto.requestId).left.map(BoundaryError.InvalidId.apply)
    passengerId <- PassengerId(dto.passengerId).left.map(BoundaryError.InvalidId.apply)
    _ <- Either.cond(dto.status == "waiting", (), BoundaryError.UnexpectedStatus(dto.status))
    yield WaitingRequest(requestId, passengerId, requiresAccessibleVehicle = false)

    snake_caseキー、デフォルト、判別子が必要なときは Configuration を提供し、configured derivationを使う:

    import io.circe.derivation.Configuration
    given Configuration = Configuration.default.withSnakeCaseMemberNames
    object RequestDto:
    given Decoder[RequestDto] = Decoder.derivedConfigured

    sealed familyには Codec.AsObject.derived が既知のsubtypeを自動導出する。単純なenum:

    enum Status derives Decoder, Encoder:
    case Waiting, EnRoute

    外部制御のstatus文字列には明示的decoderを優先し、任意の文字列をドメインenumに受け入れない。

    Play JSONを使うプロジェクトでも境界ルールは同じ: DTOに Reads / Writes、その後にドメイン型への検証付き変換。Json.format 導出を不変条件の強制とみなさない。

    スタックパターントピックガイド
    circe + DTODecoderEither マッピング境界防御
    circe + http4sEntityDecoder で DTO、ハンドラでドメイン変換境界防御
    circe + イベント外向きイベント DTO のみ codec永続化、集約、イベント

    doobieはSQLアダプター向けであり、ドメインモデリング向けではない。

    Read / Write インスタンスはinfrastructureの行case classに置く。リポジトリポートから返す前に、明示的な Either マッパーで行をドメイン型にマップする。

    トランザクションはアダプターに属する

    Section titled “トランザクションはアダプターに属する”

    ドメイン遷移内ではなく、アダプターまたはユースケース境界で transact(xa) を使う。1コマンドの状態変更とoutbox挿入は同一トランザクションを共有する。

    リポジトリtraitはポートレベルで F[_](通常 IO)を使う。ConnectionIO はアダプター実装内に留める。

    詳細は ORM アダプター を参照する。

    プロジェクトがすでにSlickを標準とするとき、SQLアダプターに使う。

    テーブル定義は infrastructure に留める

    Section titled “テーブル定義は infrastructure に留める”

    Table サブクラス、DBIO、profile importをドメインモジュールから出す。リポジトリポートは F[_] とドメイン型のみを使う。

    RequestRow(相当)をアダプター内で、ORM アダプター と同じ検証マッパーでドメイン状態に変換する。

    セッションとトランザクション

    Section titled “セッションとトランザクション”

    db.run(...transactionally) をアダプターが所有する。DatabaseDBIO をユースケースに渡さない。

    ドメインマッピング中のlazy loadや外部キー関係のナビゲーションを避け、必要な状態の列を明示的にクエリする。

    FS2は読み取り側のストリームポート、outboxディスパッチ、プロジェクション向けに使う。

    ストリームをドメインから出す

    Section titled “ストリームをドメインから出す”

    ドメイン遷移は Either とイベントリストを返す。アダプターが永続化ログやoutboxテーブル上の Stream[F, A] を公開する。

    ストリーム要素には型付きエラーを優先する

    Section titled “ストリーム要素には型付きエラーを優先する”

    Stream[F, Either[StreamError, DomainEvent]] はマッパーとデコード失敗を明示的に保つ。メトリクスとデッドレターポリシーなしに handleErrorWith(_ => Stream.empty) で失敗を飲み込まない。

    interruptWhen またはファイバキャンセルでストリームをコンパイルし、コンシューマ切断時にDBポーリングを止める。

    詳細は ストリームと継続クエリ を参照する。

    eu.timepit.refined は境界または単一フィールド不変条件向けの検証付きプリミティブnewtypeに使う。検証メッセージをドメイン固有にする必要があるドメインモジュールでは、明示的 Either ファクトリ付きopaque typeを優先する。

    • 形式ルール付きのconfigキー、クエリパラメータ、DTOフィールド(非空、UUID、正のInt)
    • 段階的導入: 完全なドメインモデリング前にレガシー String / Int 列をラップする
    • 複数フィールドまたは状態依存ルール — ドメイン型と遷移を使う
    • ORMマッピングがrefined述語を曖昧にする永続化集約ルート
    import eu.timepit.refined.api.*
    import eu.timepit.refined.collection.NonEmpty
    import eu.timepit.refined.refineEither
    type NonEmptyString = String Refined NonEmpty
    def parseRequestId(raw: String): Either[BoundaryError, NonEmptyString] =
    refineEither[NonEmpty](raw).left.map(_ => BoundaryError.EmptyId("request_id"))

    refined DTOフィールドを、アダプター境界で明示的エラー ADT付きopaqueドメインIDにマップする。境界防御ドメインマクロ も参照する。

    完全なパターンは PII 保護 を優先する。本節は資格情報とAPIキー向けのScala固有デフォルトを扱う。

    ドメインまたはユースケース層に生の String でシークレットを置かない。toString を制限したopaque type、あるいは生値を決してログしない専用wrapperを優先する。

    final class ApiToken private (private val value: String):
    override def toString: String = "ApiToken(***)"
    object ApiToken:
    def parse(raw: String): Either[BoundaryError, ApiToken] =
    if raw.trim.isEmpty then Left(BoundaryError.EmptyField("api_token"))
    else Right(new ApiToken(raw.trim))
    extension (token: ApiToken) def expose: String = token.value

    シークレット値の露出はHTTP / auth / payment境界の狭いアダプター関数(exposevalue)に限定する。露出した値をerror ADTに含めない。

    スタックパターントピックガイド
    opaque secret + アダプターauth モジュールのみ exposePII 保護
    ログtoken フィールドをログしない。構造化 *** プレースホルダロギングとメトリクス
    PII vs secrets個人データは redacted 型、資格情報は secret wrapperPII 保護

    検出のみ: pureconfig のsecret loader — 境界で検証し、ドメインコード実行前にopaque型へマップする。

    プロジェクトがすでに依存している場合、またはプロパティテストが入力全体の法則を最も明確にカバーできる場合に使う。

    Test スコープに置く。無効なドメイン状態を直接構築するより、publicコンストラクタを呼ぶgeneratorを優先する。

    import org.scalacheck.Prop.forAll
    import org.scalacheck.Gen
    property("valid ids construct") {
    forAll(nonEmptyStringGen) { raw =>
    RequestId(raw.trim).isRight
    }
    }

    generator設計、状態プロパティ、CI予算、regressionファイルは プロパティベーステスト を参照する。

    PureConfigは設定ファイルを読む。ドメインコマンドを読まない。

    デフォルトを明示的に文書化したcase classに設定をロードし、ドメイン型へ検証する。

    起動時ログされる平文configフィールドにシークレットを置かない。環境別secretプロバイダとredacting wrapperを使う。

    境界防御 も参照する。

    スタックパターントピックガイド
    pureconfig + 起動config case class → ドメイン検証境界防御
    pureconfig + secrets読み込み後すぐ opaque 型へPII 保護