[{"data":1,"prerenderedAt":963},["ShallowReactive",2],{"navigation":3,"doc-/backend/conventions":217,"docs-all-paths":908},[4,19,39,63,87,111,127,195,211],{"title":5,"path":6,"stem":7,"children":8},"Welcome","/welcome","01.welcome",[9,11,15],{"title":5,"path":6,"stem":10},"01.welcome/index",{"title":12,"path":13,"stem":14},"Astar とは（5分）","/welcome/product-in-5-min","01.welcome/01.product-in-5-min",{"title":16,"path":17,"stem":18},"事業文脈","/welcome/business-context","01.welcome/02.business-context",{"title":20,"path":21,"stem":22,"children":23},"Getting Started","/getting-started","02.getting-started",[24,27,31,35],{"title":25,"path":21,"stem":26},"はじめかた","02.getting-started/index",{"title":28,"path":29,"stem":30},"リポジトリ取得と環境構築","/getting-started/repo-setup","02.getting-started/01.repo-setup",{"title":32,"path":33,"stem":34},"日々の開発ループ","/getting-started/dev-loop","02.getting-started/02.dev-loop",{"title":36,"path":37,"stem":38},"検証の 4 層","/getting-started/verification-tiers","02.getting-started/03.verification-tiers",{"title":40,"path":41,"stem":42,"children":43},"Architecture","/architecture","03.architecture",[44,47,51,55,59],{"title":45,"path":41,"stem":46},"アーキテクチャ","03.architecture/index",{"title":48,"path":49,"stem":50},"マルチテナンシーと RLS","/architecture/multi-tenancy-rls","03.architecture/01.multi-tenancy-rls",{"title":52,"path":53,"stem":54},"認可モデル","/architecture/authorization","03.architecture/02.authorization",{"title":56,"path":57,"stem":58},"AI サブシステム","/architecture/ai-subsystem","03.architecture/03.ai-subsystem",{"title":60,"path":61,"stem":62},"データモデル","/architecture/data-model","03.architecture/04.data-model",{"title":64,"path":65,"stem":66,"children":67},"Backend","/backend","04.backend",[68,71,75,79,83],{"title":69,"path":65,"stem":70},"バックエンド","04.backend/index",{"title":72,"path":73,"stem":74},"層構造","/backend/layering","04.backend/01.layering",{"title":76,"path":77,"stem":78},"規約","/backend/conventions","04.backend/02.conventions",{"title":80,"path":81,"stem":82},"テスト","/backend/testing","04.backend/03.testing",{"title":84,"path":85,"stem":86},"マイグレーション","/backend/migrations","04.backend/04.migrations",{"title":88,"path":89,"stem":90,"children":91},"Frontend","/frontend","05.frontend",[92,95,99,103,107],{"title":93,"path":89,"stem":94},"フロントエンド","05.frontend/index",{"title":96,"path":97,"stem":98},"モジュール内部構造","/frontend/module-anatomy","05.frontend/01.module-anatomy",{"title":100,"path":101,"stem":102},"API アクセスの一本道","/frontend/api-access-chain","05.frontend/02.api-access-chain",{"title":104,"path":105,"stem":106},"i18n","/frontend/i18n","05.frontend/03.i18n",{"title":108,"path":109,"stem":110},"デザインシステム","/frontend/design-system","05.frontend/04.design-system",{"title":112,"path":113,"stem":114,"children":115},"Go Agent And Cli","/go-agent-and-cli","06.go-agent-and-cli",[116,119,123],{"title":117,"path":113,"stem":118},"Go agent と CLI","06.go-agent-and-cli/index",{"title":120,"path":121,"stem":122},"Go agent","/go-agent-and-cli/go-agent","06.go-agent-and-cli/01.go-agent",{"title":124,"path":125,"stem":126},"CLI","/go-agent-and-cli/cli","06.go-agent-and-cli/02.cli",{"title":128,"path":129,"stem":130,"children":131},"Modules","/modules","07.modules",[132,135,139,143,147,151,155,159,163,167,171,175,179,183,187,191],{"title":133,"path":129,"stem":134},"モジュール","07.modules/index",{"title":136,"path":137,"stem":138},"テーブル","/modules/table","07.modules/01.table",{"title":140,"path":141,"stem":142},"ドキュメントツリー","/modules/document","07.modules/02.document",{"title":144,"path":145,"stem":146},"AIチャット","/modules/ai","07.modules/03.ai",{"title":148,"path":149,"stem":150},"認証・権限","/modules/auth","07.modules/04.auth",{"title":152,"path":153,"stem":154},"AI取込","/modules/organize","07.modules/05.organize",{"title":156,"path":157,"stem":158},"ストレージ","/modules/storage","07.modules/06.storage",{"title":160,"path":161,"stem":162},"NAS同期","/modules/sync","07.modules/07.sync",{"title":164,"path":165,"stem":166},"ワークスペース","/modules/workspace","07.modules/08.workspace",{"title":168,"path":169,"stem":170},"テナントとメンバーシップ","/modules/tenant-membership","07.modules/09.tenant-membership",{"title":172,"path":173,"stem":174},"検索","/modules/search","07.modules/10.search",{"title":176,"path":177,"stem":178},"表示ビュー","/modules/display","07.modules/11.display",{"title":180,"path":181,"stem":182},"テンプレート","/modules/template","07.modules/12.template",{"title":184,"path":185,"stem":186},"メール","/modules/mail","07.modules/13.mail",{"title":188,"path":189,"stem":190},"電話","/modules/phone","07.modules/14.phone",{"title":192,"path":193,"stem":194},"リアルタイム","/modules/realtime","07.modules/15.realtime",{"title":196,"path":197,"stem":198,"children":199},"Infra And Deploy","/infra-and-deploy","08.infra-and-deploy",[200,203,207],{"title":201,"path":197,"stem":202},"インフラとデプロイ","08.infra-and-deploy/index",{"title":204,"path":205,"stem":206},"環境の種類","/infra-and-deploy/environments","08.infra-and-deploy/01.environments",{"title":208,"path":209,"stem":210},"セルフホスト構成","/infra-and-deploy/self-host","08.infra-and-deploy/02.self-host",{"title":212,"path":213,"stem":214,"children":215},"docs-map","/docs-map","09.docs-map/index",[216],{"title":212,"path":213,"stem":214},{"id":218,"title":76,"body":219,"description":902,"extension":903,"meta":904,"navigation":905,"path":77,"seo":906,"stem":78,"__hash__":907},"docs/04.backend/02.conventions.md",{"type":220,"value":221,"toc":890},"minimark",[222,225,238,250,253,284,290,293,450,460,464,482,486,506,567,571,679,682,686,697,721,835,862,871,874,886],[223,224,76],"h1",{"id":76},[226,227,228,229,233,234,237],"p",{},"このページの内容は ",[230,231,232],"code",{},"docs/backend/conventions.md","（詳細版）と ",[230,235,236],{},"backend/CLAUDE.md","（要約版）が正本です。ここでは新人がまず知っておくべき部分を抜き出します。",[239,240,242,246,247],"h2",{"id":241},"番人-と-自衛",[243,244,245],"span",{},"番人"," と ",[243,248,249],{},"自衛",[226,251,252],{},"正本ドキュメントの規約には、それぞれ次のタグが付いています。",[254,255,256,273],"ul",{},[257,258,259,264,265,268,269,272],"li",{},[260,261,262],"strong",{},[243,263,245],{},": ArchUnit テスト・lint・deny 設定などが",[260,266,267],{},"機械的に","違反を検知する。破ると CI（または ",[230,270,271],{},"/backend-test-reports"," の guardTest）が落ちる。",[257,274,275,279,280,283],{},[260,276,277],{},[243,278,249],{},": 破ってもビルドは通る。検知するのはコードレビューだけ。「機械が守ってくれないから軽視していい」ではなく、",[260,281,282],{},"むしろレビューでより厳しく見るべき","、という意味のタグです。",[226,285,286,287,289],{},"このページでも同じタグを使います。新人のうちは ",[243,288,249],{}," タグが付いた規約ほど「なぜこれが規約になっているか」の背景（多くは実インシデント）まで読んでおくと、レビューで指摘される前に気付けます。",[239,291,292],{"id":292},"命名規約",[294,295,296,312],"table",{},[297,298,299],"thead",{},[300,301,302,306,309],"tr",{},[303,304,305],"th",{},"種類",[303,307,308],{},"パターン",[303,310,311],{},"例",[313,314,315,331,346,366,381,400,416,435],"tbody",{},[300,316,317,321,326],{},[318,319,320],"td",{},"Controller",[318,322,323],{},[230,324,325],{},"{Entity}Controller",[318,327,328],{},[230,329,330],{},"TagController",[300,332,333,336,341],{},[318,334,335],{},"Service",[318,337,338],{},[230,339,340],{},"{Entity}Service",[318,342,343],{},[230,344,345],{},"TagService",[300,347,348,351,361],{},[318,349,350],{},"Exposed Table",[318,352,353,356,357,360],{},[230,354,355],{},"{Entity}sTable","（object、",[230,358,359],{},"persistence/table/"," 配下）",[318,362,363],{},[230,364,365],{},"TagsTable",[300,367,368,371,376],{},[318,369,370],{},"Exposed Repo 実装",[318,372,373],{},[230,374,375],{},"{Entity}ExposedRepositoryImpl",[318,377,378],{},[230,379,380],{},"TagExposedRepositoryImpl",[300,382,383,386,395],{},[318,384,385],{},"Config interface",[318,387,388,391,392,360],{},[230,389,390],{},"{Concern}Config","（",[230,393,394],{},"domain/config/",[318,396,397],{},[230,398,399],{},"AICreditConfig",[300,401,402,405,411],{},[318,403,404],{},"Mapper",[318,406,407,410],{},[230,408,409],{},"{Entity}Mapper","（接頭辞なし）",[318,412,413],{},[230,414,415],{},"UserMapper",[300,417,418,421,426],{},[318,419,420],{},"Public ID (UUID)",[318,422,423],{},[230,424,425],{},"{entity}Id",[318,427,428,431,432],{},[230,429,430],{},"tenantId",", ",[230,433,434],{},"userId",[300,436,437,440,445],{},[318,438,439],{},"Internal ID (BIGINT)",[318,441,442],{},[230,443,444],{},"internal{Entity}Id",[318,446,447],{},[230,448,449],{},"internalTenantId",[226,451,452,455,456,459],{},[230,453,454],{},"EntityId\u003CT>","（型安全な ID ラッパー）はドメインモデルの ID フィールドすべてに必須です。生の ",[230,457,458],{},"UUID"," フィールドを直接持たせません。",[461,462,463],"h3",{"id":463},"ドメイン駆動命名",[226,465,466,467,474,475,477,478,481],{},"新しい概念（フィールド・enum・フラグ・クラス）に名前を付ける前に、",[260,468,469,470,473],{},"まず既存コードで同種の概念がどう呼ばれているかを ",[230,471,472],{},"grep"," で確認","します。Astar の命名は業務用語（法律事務所のドメイン）に寄せてあり、汎用的すぎる名前や独自の略語・prefix は避けます。たとえばリソースの所有関係を表すなら既存の ",[230,476,430],{}," / ",[230,479,480],{},"createdByUserId"," の命名パターンに合わせ、「今回だけの」新しい命名規則を発明しないでください。表記ゆれ（同じ概念に複数の呼び方が並立する状態）はレビューで指摘対象です。",[239,483,485],{"id":484},"dto-規約","DTO 規約",[226,487,488,489,498,499,391,502,505],{},"DTO は ",[260,490,491,494,495],{},[230,492,493],{},"kotlinx.serialization"," の ",[230,496,497],{},"@Serializable","、Jackson ではありません。HTTP メッセージコンバータは ",[230,500,501],{},"KotlinSerializationJsonHttpMessageConverter",[230,503,504],{},"shared/infrastructure/config/WebMvcConfig.kt","）1 本だけが登録されています。",[254,507,508,523,546],{},[257,509,510,511,514,515,518,519,522],{},"ワイヤー上のキー名を変えたい場合は ",[230,512,513],{},"@SerialName"," を使う。プレーンな kotlinx のリネーム + ",[230,516,517],{},"ignoreUnknownKeys = true"," の組み合わせは、エラーにならず",[260,520,521],{},"そのフィールドを黙って読み捨てる","ので危険です。",[257,524,525,527,528,531,532,535,536,539,540,542,543,545],{},[243,526,249],{}," ",[230,529,530],{},"api/dto/*.kt"," の一部（",[230,533,534],{},"core/devicelog/api/dto/DeviceLogDtos.kt"," や ",[230,537,538],{},"core/ai/api/dto/AiRunDtos.kt"," など）には現時点で ",[230,541,497],{}," が付いていないファイルが約 10 本残っています。ガードは存在しません。もしこれらの DTO クラスが将来 Controller の戻り値型として使われると、コンパイルは通ったまま実行時にエンコードで例外になります。新しい DTO を書くときは ",[230,544,497],{}," を付け忘れないでください。",[257,547,548,551,552,555,556,559,560,566],{},[230,549,550],{},"api/dto"," の中で ",[230,553,554],{},"BigDecimal"," を直接使わない — contextual な ",[230,557,558],{},"BigDecimalSerializer"," 型を使います ",[243,561,562,563],{},"番人: ",[230,564,565],{},"ApiDtoWireFormatTest","。",[461,568,570],{"id":569},"requestresponse-の命名","Request/Response の命名",[294,572,573,584],{},[297,574,575],{},[300,576,577,579,581],{},[303,578,308],{},[303,580,311],{},[303,582,583],{},"備考",[313,585,586,604,619,633,648,663],{},[300,587,588,593,598],{},[318,589,590],{},[230,591,592],{},"{Entity}CreateRequest",[318,594,595],{},[230,596,597],{},"TagCreateRequest",[318,599,600,603],{},[230,601,602],{},"Create{Entity}Request"," は禁止",[300,605,606,611,616],{},[318,607,608],{},[230,609,610],{},"{Entity}UpdateRequest",[318,612,613],{},[230,614,615],{},"TagUpdateRequest",[318,617,618],{},"同上",[300,620,621,626,631],{},[318,622,623],{},[230,624,625],{},"{Entity}Response",[318,627,628],{},[230,629,630],{},"TagResponse",[318,632],{},[300,634,635,640,645],{},[318,636,637],{},[230,638,639],{},"PageResponse\u003CT>",[318,641,642],{},[230,643,644],{},"PageResponse\u003CRecordResponse>",[318,646,647],{},"ページネーションあり",[300,649,650,655,660],{},[318,651,652],{},[230,653,654],{},"{Entity}ListResponse",[318,656,657],{},[230,658,659],{},"TableListResponse",[318,661,662],{},"ページネーションなし・件数表示",[300,664,665,671,676],{},[318,666,667,668],{},"raw ",[230,669,670],{},"List\u003CT>",[318,672,673],{},[230,674,675],{},"List\u003CTagResponse>",[318,677,678],{},"タグ・ロールなど小規模固定セット",[226,680,681],{},"独自のページネーション形状を新しく作らないでください — 上記 3 パターンのどれかに当てはめます。",[239,683,685],{"id":684},"トランザクション規約tx-poison-doctrine","トランザクション規約（tx-poison doctrine）",[226,687,688,689,692,693,696],{},"Write メソッドは ",[230,690,691],{},"@Transactional","、Read メソッドは ",[230,694,695],{},"@Transactional(readOnly = true)","。ここまでは一般的な Spring の作法どおりです。Astar 特有なのは次の一点です。",[226,698,699,716,717,720],{},[260,700,701,704,705,535,708,711,712,715],{},[230,702,703],{},"try","/",[230,706,707],{},"catch",[230,709,710],{},"runCatching"," で ",[230,713,714],{},"REQUIRED"," トランザクション越しの呼び出しを囲んでも、共有トランザクションは保護されません。"," 例外が一度でも投げられると、その時点でトランザクションは \"rollback-only\" にマークされ、最終的な commit は ",[230,718,719],{},"UnexpectedRollbackException"," を投げます。catch していても関係ありません。",[722,723,728],"pre",{"className":724,"code":725,"language":726,"meta":727,"style":727},"language-kotlin shiki shiki-themes github-light github-dark","// NG: notifyService が例外を投げた時点で共有 @Transactional は汚染される。\n//     ここで catch していても、最後の commit が UnexpectedRollbackException で落ちる。\n@Transactional\nfun createRecord(...): Record {\n    val record = recordRepository.save(...)\n    runCatching { notifyService.notify(record) }\n    return record\n}\n\n// OK: ベストエフォートのサブ処理は RlsTransactions.requiresNew で\n//     独立したトランザクションに隔離する。呼び出し元の RLS context も\n//     新しいコネクションへ引き継がれる。\n@Transactional\nfun createRecord(...): Record {\n    val record = recordRepository.save(...)\n    runCatching { rlsTransactions.requiresNew { notifyService.notify(record) } }\n    return record\n}\n","kotlin","",[230,729,730,737,743,749,755,761,767,773,779,786,792,798,804,809,814,819,825,830],{"__ignoreMap":727},[243,731,734],{"class":732,"line":733},"line",1,[243,735,736],{},"// NG: notifyService が例外を投げた時点で共有 @Transactional は汚染される。\n",[243,738,740],{"class":732,"line":739},2,[243,741,742],{},"//     ここで catch していても、最後の commit が UnexpectedRollbackException で落ちる。\n",[243,744,746],{"class":732,"line":745},3,[243,747,748],{},"@Transactional\n",[243,750,752],{"class":732,"line":751},4,[243,753,754],{},"fun createRecord(...): Record {\n",[243,756,758],{"class":732,"line":757},5,[243,759,760],{},"    val record = recordRepository.save(...)\n",[243,762,764],{"class":732,"line":763},6,[243,765,766],{},"    runCatching { notifyService.notify(record) }\n",[243,768,770],{"class":732,"line":769},7,[243,771,772],{},"    return record\n",[243,774,776],{"class":732,"line":775},8,[243,777,778],{},"}\n",[243,780,782],{"class":732,"line":781},9,[243,783,785],{"emptyLinePlaceholder":784},true,"\n",[243,787,789],{"class":732,"line":788},10,[243,790,791],{},"// OK: ベストエフォートのサブ処理は RlsTransactions.requiresNew で\n",[243,793,795],{"class":732,"line":794},11,[243,796,797],{},"//     独立したトランザクションに隔離する。呼び出し元の RLS context も\n",[243,799,801],{"class":732,"line":800},12,[243,802,803],{},"//     新しいコネクションへ引き継がれる。\n",[243,805,807],{"class":732,"line":806},13,[243,808,748],{},[243,810,812],{"class":732,"line":811},14,[243,813,754],{},[243,815,817],{"class":732,"line":816},15,[243,818,760],{},[243,820,822],{"class":732,"line":821},16,[243,823,824],{},"    runCatching { rlsTransactions.requiresNew { notifyService.notify(record) } }\n",[243,826,828],{"class":732,"line":827},17,[243,829,772],{},[243,831,833],{"class":732,"line":832},18,[243,834,778],{},[226,836,837,391,840,843,844,847,848,851,852,857,858,861],{},[230,838,839],{},"RlsTransactions.requiresNew { }",[230,841,842],{},"shared/infrastructure/security/rls/RlsTransactions.kt","）が、通知・ログ・キャッシュウォームのようなベストエフォート処理を隔離する",[260,845,846],{},"唯一の","正しい方法です。素の ",[230,849,850],{},"@Transactional(REQUIRES_NEW)"," は凍結されていて ",[243,853,562,854],{},[230,855,856],{},"RequiresNewTransactionArchitectureTest","、直接使うと CI が落ちます。仮に使えたとしても RLS の GUC（テナントコンテキスト）を新しいコネクション上で失うため、",[230,859,860],{},"RlsTransactions"," 経由でない限り RLS チェックが壊れます。",[226,863,864,527,866,711,868,870],{},[243,865,249],{},[230,867,710],{},[230,869,714],{}," を囲むパターン自体にはガードが一切なく、社内で最も繰り返し発生してきたバグクラスです。「catch しているから安全」という直感は、このコードベースでは通用しません。",[239,872,873],{"id":873},"参照",[254,875,876,881],{},[257,877,878,879],{},"詳細版・全パターン: リポジトリの ",[230,880,232],{},[257,882,883,884],{},"要約版: リポジトリの ",[230,885,236],{},[887,888,889],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":727,"searchDepth":739,"depth":739,"links":891},[892,894,897,900,901],{"id":241,"depth":739,"text":893},"番人 と 自衛",{"id":292,"depth":739,"text":292,"children":895},[896],{"id":463,"depth":745,"text":463},{"id":484,"depth":739,"text":485,"children":898},[899],{"id":569,"depth":745,"text":570},{"id":684,"depth":739,"text":685},{"id":873,"depth":739,"text":873},"命名・DTO・kotlinx.serialization・トランザクション規約（tx-poison doctrine）とドメイン駆動命名。番人（機械的強制）と自衛（規律のみ）の区別。","md",{},{"title":76},{"title":76,"description":902},"Xml6w3NI9R9Q0G22Dw-GJywuDfyKvlLZQLyKRZ6eg28",[909,911,912,914,915,916,917,918,919,920,921,922,924,925,926,927,928,930,931,932,933,934,936,938,940,941,942,943,944,945,946,947,948,949,950,951,952,953,954,955,956,958,959,960,961],{"path":13,"title":910},"Astar とは何か（5分で）",{"path":17,"title":16},{"path":6,"title":913},"ようこそ",{"path":29,"title":28},{"path":33,"title":32},{"path":37,"title":36},{"path":21,"title":25},{"path":49,"title":48},{"path":53,"title":52},{"path":57,"title":56},{"path":61,"title":60},{"path":41,"title":923},"システムアーキテクチャ",{"path":73,"title":72},{"path":77,"title":76},{"path":81,"title":80},{"path":85,"title":84},{"path":65,"title":929},"バックエンドガイド",{"path":97,"title":96},{"path":101,"title":100},{"path":105,"title":104},{"path":109,"title":108},{"path":89,"title":935},"フロントエンドガイド",{"path":121,"title":937},"Go agent — NAS 同期エージェント",{"path":125,"title":939},"astar CLI",{"path":113,"title":117},{"path":137,"title":136},{"path":141,"title":140},{"path":145,"title":144},{"path":149,"title":148},{"path":153,"title":152},{"path":157,"title":156},{"path":161,"title":160},{"path":165,"title":164},{"path":169,"title":168},{"path":173,"title":172},{"path":177,"title":176},{"path":181,"title":180},{"path":185,"title":184},{"path":189,"title":188},{"path":193,"title":192},{"path":129,"title":957},"モジュール索引",{"path":205,"title":204},{"path":209,"title":208},{"path":197,"title":201},{"path":213,"title":962},"docs/ 地図",1785452454987]