メインコンテンツへ

規約

このページの内容は docs/backend/conventions.md(詳細版)と backend/CLAUDE.md(要約版)が正本です。ここでは新人がまず知っておくべき部分を抜き出します。

番人自衛

正本ドキュメントの規約には、それぞれ次のタグが付いています。

  • 番人: ArchUnit テスト・lint・deny 設定などが機械的に違反を検知する。破ると CI(または /backend-test-reports の guardTest)が落ちる。
  • 自衛: 破ってもビルドは通る。検知するのはコードレビューだけ。「機械が守ってくれないから軽視していい」ではなく、むしろレビューでより厳しく見るべき、という意味のタグです。

このページでも同じタグを使います。新人のうちは 自衛 タグが付いた規約ほど「なぜこれが規約になっているか」の背景(多くは実インシデント)まで読んでおくと、レビューで指摘される前に気付けます。

命名規約

種類パターン
Controller{Entity}ControllerTagController
Service{Entity}ServiceTagService
Exposed Table{Entity}sTable(object、persistence/table/ 配下)TagsTable
Exposed Repo 実装{Entity}ExposedRepositoryImplTagExposedRepositoryImpl
Config interface{Concern}Configdomain/config/ 配下)AICreditConfig
Mapper{Entity}Mapper(接頭辞なし)UserMapper
Public ID (UUID){entity}IdtenantId, userId
Internal ID (BIGINT)internal{Entity}IdinternalTenantId

EntityId<T>(型安全な ID ラッパー)はドメインモデルの ID フィールドすべてに必須です。生の UUID フィールドを直接持たせません。

ドメイン駆動命名

新しい概念(フィールド・enum・フラグ・クラス)に名前を付ける前に、まず既存コードで同種の概念がどう呼ばれているかを grep で確認します。Astar の命名は業務用語(法律事務所のドメイン)に寄せてあり、汎用的すぎる名前や独自の略語・prefix は避けます。たとえばリソースの所有関係を表すなら既存の tenantId / createdByUserId の命名パターンに合わせ、「今回だけの」新しい命名規則を発明しないでください。表記ゆれ(同じ概念に複数の呼び方が並立する状態)はレビューで指摘対象です。

DTO 規約

DTO は kotlinx.serialization@Serializable、Jackson ではありません。HTTP メッセージコンバータは KotlinSerializationJsonHttpMessageConvertershared/infrastructure/config/WebMvcConfig.kt)1 本だけが登録されています。

  • ワイヤー上のキー名を変えたい場合は @SerialName を使う。プレーンな kotlinx のリネーム + ignoreUnknownKeys = true の組み合わせは、エラーにならずそのフィールドを黙って読み捨てるので危険です。
  • 自衛 api/dto/*.kt の一部(core/devicelog/api/dto/DeviceLogDtos.ktcore/ai/api/dto/AiRunDtos.kt など)には現時点で @Serializable が付いていないファイルが約 10 本残っています。ガードは存在しません。もしこれらの DTO クラスが将来 Controller の戻り値型として使われると、コンパイルは通ったまま実行時にエンコードで例外になります。新しい DTO を書くときは @Serializable を付け忘れないでください。
  • api/dto の中で BigDecimal を直接使わない — contextual な BigDecimalSerializer 型を使います 番人: ApiDtoWireFormatTest

Request/Response の命名

パターン備考
{Entity}CreateRequestTagCreateRequestCreate{Entity}Request は禁止
{Entity}UpdateRequestTagUpdateRequest同上
{Entity}ResponseTagResponse
PageResponse<T>PageResponse<RecordResponse>ページネーションあり
{Entity}ListResponseTableListResponseページネーションなし・件数表示
raw List<T>List<TagResponse>タグ・ロールなど小規模固定セット

独自のページネーション形状を新しく作らないでください — 上記 3 パターンのどれかに当てはめます。

トランザクション規約(tx-poison doctrine)

Write メソッドは @Transactional、Read メソッドは @Transactional(readOnly = true)。ここまでは一般的な Spring の作法どおりです。Astar 特有なのは次の一点です。

try/catchrunCatchingREQUIRED トランザクション越しの呼び出しを囲んでも、共有トランザクションは保護されません。 例外が一度でも投げられると、その時点でトランザクションは "rollback-only" にマークされ、最終的な commit は UnexpectedRollbackException を投げます。catch していても関係ありません。

// NG: notifyService が例外を投げた時点で共有 @Transactional は汚染される。
//     ここで catch していても、最後の commit が UnexpectedRollbackException で落ちる。
@Transactional
fun createRecord(...): Record {
    val record = recordRepository.save(...)
    runCatching { notifyService.notify(record) }
    return record
}

// OK: ベストエフォートのサブ処理は RlsTransactions.requiresNew で
//     独立したトランザクションに隔離する。呼び出し元の RLS context も
//     新しいコネクションへ引き継がれる。
@Transactional
fun createRecord(...): Record {
    val record = recordRepository.save(...)
    runCatching { rlsTransactions.requiresNew { notifyService.notify(record) } }
    return record
}

RlsTransactions.requiresNew { }shared/infrastructure/security/rls/RlsTransactions.kt)が、通知・ログ・キャッシュウォームのようなベストエフォート処理を隔離する唯一の正しい方法です。素の @Transactional(REQUIRES_NEW) は凍結されていて 番人: RequiresNewTransactionArchitectureTest、直接使うと CI が落ちます。仮に使えたとしても RLS の GUC(テナントコンテキスト)を新しいコネクション上で失うため、RlsTransactions 経由でない限り RLS チェックが壊れます。

自衛 runCatchingREQUIRED を囲むパターン自体にはガードが一切なく、社内で最も繰り返し発生してきたバグクラスです。「catch しているから安全」という直感は、このコードベースでは通用しません。

参照

  • 詳細版・全パターン: リポジトリの docs/backend/conventions.md
  • 要約版: リポジトリの backend/CLAUDE.md