共通コーディング規約
関連ドキュメント
Section titled “関連ドキュメント”本ドキュメントと合わせて以下を参照すること。
| ドキュメント | 内容 |
|---|---|
| ArchitecturePolicy_Common.md | 共通アーキテクチャ方針 |
| AuthenticationSpecification_Common.md | 共通認証仕様 |
| 機能別コーディング規約(例:CodingConventions_Coordination_Sprint1.md) | 機能・フェーズ固有のパッケージ名・APIパス設計等 |
本ドキュメントは、Java Spring Boot 実装における共通コーディング規約を定義する。
Claude Code によるコード生成時は、本ドキュメントの内容を遵守し、可読性・保守性・一貫性のあるコードを生成すること。
本規約の対象は以下とする。
- Java ソースコード
- Spring Boot 関連コード
- Handler(Primary Adapter)
- UseCase
- Domain Model(Entity・値オブジェクト)
- Port(インターフェース)
- Repository 実装(Secondary Adapter)
- MyBatis Mapper(インターフェース + XML)
- Exception
- Unit Test / Integration Test
- 設定ファイル
2. 基本方針
Section titled “2. 基本方針”コード作成時は以下を基本方針とする。
- 読みやすさを優先する
- 責務を明確に分離する
- 重複コードを避ける
- 過度に複雑な設計を避ける
- ヘキサゴナルアーキテクチャの構成を優先する
- OpenAPI yaml と DB create SQL の定義を尊重する
- 業務仕様が不明な場合は、勝手に仕様を追加せずコメントで仮定を明示する
- コンパイル可能なコードを生成する
- 未使用 import を残さない
- TODO コメントを乱用しない
- Google Java Format(Spotless)に準拠したフォーマットとする
3. Java バージョン
Section titled “3. Java バージョン”Java 25 を前提とする。
Java 25 で利用可能な構文は使用してよいが、可読性を損なう書き方は避ける。
使用してよい例(var):
var result = createFeatureAUseCase.execute(cmd);ただし、型が分かりにくくなる場合は var を使用しない。
避ける例:
var data = repository.findById(id).orElseThrow();推奨例:
FeatureA featureA = repository.findById(id) .orElseThrow(() -> new FeatureANotFoundException(id));record・sealed interface・switch 式(パターンマッチング)・テキストブロック等は積極的に活用する。
4. 文字コード・改行コード
Section titled “4. 文字コード・改行コード”ソースコードおよび設定ファイルは以下とする。
| 項目 | 規約 |
|---|---|
| 文字コード | UTF-8 |
| 改行コード | LF |
| インデント | 半角スペース 2 つ(Google Java Format 準拠) |
| タブ文字 | 使用禁止 |
| 1 行の文字数目安 | 100 文字以内(Google Java Format 準拠) |
フォーマットは ./gradlew spotlessApply で自動整形する。
5. パッケージ命名規約
Section titled “5. パッケージ命名規約”パッケージ名はすべて小文字とする。
組織識別子は com.intent_exchange とする。機能識別子は機能ごとに定める(機能別コーディング規約ドキュメントを参照すること)。
推奨(パッケージ構成の例):
com.intent_exchange.<feature>.configcom.intent_exchange.<feature>.generated.apicom.intent_exchange.<feature>.generated.modelcom.intent_exchange.<feature>.handlercom.intent_exchange.<feature>.handler.problemcom.intent_exchange.<feature>.handler.responsecom.intent_exchange.<feature>.usecasecom.intent_exchange.<feature>.domain.modelcom.intent_exchange.<feature>.domain.portcom.intent_exchange.<feature>.domain.exceptioncom.intent_exchange.<feature>.domain.commoncom.intent_exchange.<feature>.infrastructure.persistence.postgrescom.intent_exchange.<feature>.infrastructure.persistence.postgres.mapper禁止:
com.example.FeatureA.Handlercom.intent_exchange.<feature>App.Service各パッケージには package-info.java を作成し、パッケージの責務を記述する。
機能ごとの具体的なパッケージ名は、機能別コーディング規約ドキュメントを参照すること。
6. クラス命名規約
Section titled “6. クラス命名規約”クラス名は UpperCamelCase とする。
| 種別 | 命名規約 | 例 |
|---|---|---|
| Handler(Primary Adapter) | XxxHandler | FeatureAHandler |
| UseCase | XxxUseCase | CreateFeatureAUseCase |
| Domain Entity(sealed 状態型) | 状態ごとに意味ある名称 | FlightPlan.Draft / FlightPlan.Approved |
| 値オブジェクト | 意味ある名称 | FeatureAId / EntityValue |
| Port(Repository I/F) | XxxRepository | FeatureARepository |
| Port(TransactionManager) | TransactionManager | TransactionManager |
| Repository 実装 | XxxRepositoryImpl | FeatureARepositoryImpl |
| MyBatis Mapper | XxxMapper | FeatureAMapper |
| DB 行レコード | XxxRecord | FeatureARecord |
| レスポンス変換 Mapper | XxxResponseMapper | FeatureAResponseMapper |
| ドメイン例外 | XxxException | FeatureANotFoundException |
| Config | XxxConfig | AppConfig |
| Test | XxxTest | CreateFeatureAUseCaseTest |
7. メソッド命名規約
Section titled “7. メソッド命名規約”メソッド名は lowerCamelCase とする。
処理内容が分かる名称を使用する。
推奨(UseCase):
public FeatureA execute(CreateFeatureACommand command) { ... }推奨(Repository):
Optional<FeatureA> findById(FeatureAId id);Optional<FeatureA> findByIdForUpdate(FeatureAId id);List<FeatureA> findAll();FeatureA save(FeatureA featureA);void deleteById(FeatureAId id);避ける例:
public FeatureA get(FeatureAId id) { ... }public FeatureA exec(CreateFeatureACommand cmd) { ... }public void process(FeatureAId id) { ... }8. 変数命名規約
Section titled “8. 変数命名規約”変数名は lowerCamelCase とする。
意味が分かる名称を使用し、過度な省略は避ける。
推奨:
FeatureA featureA = repository.findById(id) .orElseThrow(() -> new FeatureANotFoundException(id.value()));
List<FeatureAResponse> responses = entities.stream() .map(FeatureAResponseMapper::from) .toList();避ける例:
FeatureA f = repository.findById(id).orElseThrow();
List<FeatureAResponse> list = data.stream() .map(FeatureAResponseMapper::from) .toList();ただし、ラムダ式や短いスコープでは一般的な短縮名を許容する。
entities.stream() .map(e -> FeatureAResponseMapper.from(e)) .toList();9. 定数命名規約
Section titled “9. 定数命名規約”定数は以下の形式とする。
static final- 大文字スネークケース
例:
private static final String DEFAULT_CREATED_BY = "system";private static final int DEFAULT_PAGE_SIZE = 20;マジックナンバーや固定文字列を処理内に直接書かない。
避ける例:
if (request.getStatusCode() == 9) { throw new IllegalArgumentException("invalid status");}推奨例(ただし状態は sealed interface で型として表現することを優先する):
// sealed interface で状態を型として表現する(アーキテクチャ方針 6.2 参照)return switch (entity) { case FlightPlan.Draft d -> "下書き"; case FlightPlan.Approved a -> "承認済み"; // ...};10. Handler 実装規約
Section titled “10. Handler 実装規約”Handler は HTTP リクエスト/レスポンスの制御と UseCase 呼び出しのみを行う。
Handler に業務ロジックを書かない。
10.1 基本構成
Section titled “10.1 基本構成”Handler では以下を実施する。
- 生成 API インターフェース(
XxxApi)のimplements - 意味的 parse(基本型 → ドメイン値オブジェクトへの変換)
- UseCase 呼び出し
- ドメイン型 → 生成 DTO への変換(
XxxResponseMapperを使用) - レスポンス返却
例:
@Validated@RestControllerpublic class FeatureAHandler implements FeatureAsApi {
private final CreateFeatureAUseCase createFeatureAUseCase; private final GetFeatureAUseCase getFeatureAUseCase;
public FeatureAHandler( CreateFeatureAUseCase createFeatureAUseCase, GetFeatureAUseCase getFeatureAUseCase) { this.createFeatureAUseCase = createFeatureAUseCase; this.getFeatureAUseCase = getFeatureAUseCase; }
@Override public ResponseEntity<FeatureAResponse> createFeatureA( CreateFeatureARequest request) { var featureA = createFeatureAUseCase.execute( new CreateFeatureACommand(request.getTitle())); return ResponseEntity.status(HttpStatus.CREATED) .body(FeatureAResponseMapper.from(featureA)); }
@Override public ResponseEntity<FeatureAResponse> getFeatureA(Long id) { return ResponseEntity.ok( FeatureAResponseMapper.from(getFeatureAUseCase.execute(id))); }}10.2 Handler で禁止すること
Section titled “10.2 Handler で禁止すること”Handler では以下を禁止する。
// 禁止: Handler で Repository を直接呼び出すprivate final FeatureARepository featureARepository;
// 禁止: Handler に業務ロジックを書くif (request.getStatus() == 1) { ... }
// 禁止: ドメイン例外に HTTP ステータスを設定する// (GlobalExceptionHandler の責務)
// 禁止: Handler ごとに try-catch を多用するtry { ... } catch (Exception e) { ... }
// 禁止: UseCase に HttpServletRequest を渡すuseCase.execute(request, httpServletRequest);例外処理は GlobalExceptionHandler で実施する。
11. UseCase 実装規約
Section titled “11. UseCase 実装規約”UseCase はユースケース単位の処理フローを担当する。
1 ユースケース = 1 クラス = 1 execute メソッド。
11.1 基本構成
Section titled “11.1 基本構成”public class CreateFeatureAUseCase {
private static final Logger log = LoggerFactory.getLogger(CreateFeatureAUseCase.class);
private final FeatureARepository featureARepository; private final TransactionManager transactionManager;
public CreateFeatureAUseCase( FeatureARepository featureARepository, TransactionManager transactionManager) { this.featureARepository = featureARepository; this.transactionManager = transactionManager; }
public FeatureA execute(CreateFeatureACommand command) { var featureA = transactionManager.execute( () -> featureARepository.save(FeatureA.create(command.title()))); log.info("event=featureA.created id={}", featureA.id()); return featureA; }}11.2 UseCase で禁止すること
Section titled “11.2 UseCase で禁止すること”UseCase では以下を禁止する。
// 禁止: HTTP ステータスを UseCase で直接扱うreturn ResponseEntity.ok(response);
// 禁止: HttpServletRequest に依存するpublic FeatureA execute(HttpServletRequest request) { ... }
// 禁止: @Transactional を使う(TransactionManager を使う)@Transactionalpublic FeatureA execute(...) { ... }
// 禁止: @Component/@Service を付与する@Servicepublic class CreateFeatureAUseCase { ... }
// 禁止: io.micrometer / io.opentelemetry を import するimport io.micrometer.core.instrument.MeterRegistry;12. Domain Model 実装規約
Section titled “12. Domain Model 実装規約”Domain は Spring・MyBatis・Infrastructure に一切依存しない。
12.1 値オブジェクト(record + スマートコンストラクタ)
Section titled “12.1 値オブジェクト(record + スマートコンストラクタ)”record FeatureAId(long value) { public FeatureAId { if (value <= 0) throw new InvalidFeatureAIdException(); }}12.2 状態を持つ集約(sealed interface + record)
Section titled “12.2 状態を持つ集約(sealed interface + record)”/// 飛行調整ドメインモデル。sealed interface で状態ごとにクラスを分ける。public sealed interface FlightPlan permits FlightPlan.Draft, FlightPlan.Submitted, FlightPlan.Approved, FlightPlan.Rejected {
FlightPlanId id();
FlightRoute route();
record Draft(FlightPlanId id, FlightRoute route) implements FlightPlan { public Draft { if (id == null) throw new InvalidFlightPlanIdException(); if (route == null) throw new InvalidFlightRouteException(); }
public Submitted submit() { return new Submitted(id, route); } // approve() は存在しない → Draft から直接承認不可(コンパイルエラー) }
record Submitted(FlightPlanId id, FlightRoute route) implements FlightPlan { public Approved approve() { return new Approved(id, route); } public Rejected reject(String reason) { return new Rejected(id, route, reason); } }
record Approved(FlightPlanId id, FlightRoute route) implements FlightPlan {}
record Rejected(FlightPlanId id, FlightRoute route, String reason) implements FlightPlan {}}12.3 Domain Model で禁止すること
Section titled “12.3 Domain Model で禁止すること”Domain Model では以下を禁止する。
// 禁止: ORM アノテーションの付与@Entity@Table(name = "feature_a")public class FeatureA { ... }
// 禁止: Bean Validation アノテーションの付与@NotNull@Size(max = 100)private String title;
// 禁止: Spring の依存@Autowiredprivate FeatureARepository repository;
// 禁止: Infrastructure を import するimport com.<org>.<feature>.infrastructure.persistence.postgres.FeatureARepositoryImpl;13. Port 実装規約
Section titled “13. Port 実装規約”Port インターフェースは domain.port パッケージに定義する。
13.1 Repository Port
Section titled “13.1 Repository Port”/// 機能 A の Repository Port。public interface FeatureARepository {
/// 指定 ID のエンティティを取得する。存在しない場合は `Optional.empty()` を返す。 Optional<FeatureA> findById(FeatureAId id);
/// 指定 ID のエンティティを排他ロックで取得する(状態遷移・競合制御用)。 Optional<FeatureA> findByIdForUpdate(FeatureAId id);
/// 全エンティティを取得する。 List<FeatureA> findAll();
/// エンティティを保存する(INSERT または UPDATE)。 FeatureA save(FeatureA featureA);
/// 指定 ID のエンティティを削除する。 void deleteById(FeatureAId id);}13.2 TransactionManager Port
Section titled “13.2 TransactionManager Port”/// トランザクション制御の Port。Infrastructure から Spring に依存させる。public interface TransactionManager {
/// アクションをトランザクション内で実行する。 <T> T execute(Supplier<T> action);
/// トランザクション属性を指定してアクションを実行する。 <T> T execute(TransactionOptions options, Supplier<T> action);}14. Repository 実装規約
Section titled “14. Repository 実装規約”Repository 実装は infrastructure.persistence.postgres パッケージに配置する。
Port インターフェースを implements する。
/// PostgreSQL バックの FeatureARepository 実装。MyBatis を使用する。public class FeatureARepositoryImpl implements FeatureARepository {
private final FeatureAMapper featureAMapper;
public FeatureARepositoryImpl(FeatureAMapper featureAMapper) { this.featureAMapper = featureAMapper; }
@Override public Optional<FeatureA> findById(FeatureAId id) { try { return featureAMapper.findById(id.value()).map(this::toDomain); } catch (DataAccessException e) { throw new RepositoryException("failed to find featureA", e); } }
@Override public FeatureA save(FeatureA featureA) { try { return toDomain(featureAMapper.upsert(toRecord(featureA))); } catch (DataAccessException e) { throw new RepositoryException("failed to save featureA", e); } }
// ドメイン型 ↔ DB 行レコードの変換は実装クラス内に閉じる private FeatureARecord toRecord(FeatureA featureA) { ... } private FeatureA toDomain(FeatureARecord record) { ... }}14.1 Repository 命名
Section titled “14.1 Repository 命名”Repository メソッドは検索条件が分かる名前にする。
推奨:
Optional<FeatureA> findById(FeatureAId id);Optional<FeatureA> findByIdForUpdate(FeatureAId id);List<FeatureA> findByStatus(FeatureAStatus status);避ける例:
Optional<FeatureA> getData(FeatureAId id);boolean check(FeatureAId id);14.2 MyBatis Mapper
Section titled “14.2 MyBatis Mapper”MyBatis Mapper インターフェースと XML を infrastructure.persistence.postgres.mapper パッケージに配置する。
SQL は XML に記述する。パラメータは必ず #{} を使用する。
禁止:
<!-- 禁止: 文字列連結による SQL 組み立て --><select id="findByTitle" resultType="FeatureARecord"> SELECT * FROM feature_a WHERE title = '${title}'</select>推奨:
<select id="findById" resultType="FeatureARecord"> SELECT <include refid="featureAColumns"/> FROM feature_a WHERE id = #{id}</select>共有する SELECT 本体は <sql> フラグメントで重複を避ける。
15. DB 行レコード実装規約
Section titled “15. DB 行レコード実装規約”DB 行レコード(XxxRecord)は Infrastructure 層内にのみ存在する。
record クラスとして定義する。
record FeatureARecord( long id, String title, String status, @Nullable OffsetDateTime createdAt) {}DB 行レコードを Domain 層・UseCase 層・Handler 層に持ち込まない。
16. 例外実装規約
Section titled “16. 例外実装規約”ドメイン例外は domain.exception パッケージに定義する。
ビジネスルール違反は BusinessRuleException を継承する。
FeatureANotFoundException のような「対象データが存在しない」例外も BusinessRuleException を継承する。これは、対象データが存在しないことをシステムエラーではなくビジネスルール上の状態として扱い、HTTP 404 として表現するためである。
/// ビジネスルール違反の基底例外。public class BusinessRuleException extends RuntimeException { public BusinessRuleException(String message) { super(message); }}
/// 対象エンティティが見つからない場合の例外。// 「存在しない」はビジネスルール上の状態であるため BusinessRuleException を継承する(HTTP 404)。public class FeatureANotFoundException extends BusinessRuleException { public FeatureANotFoundException(long id) { super("エンティティが見つかりません。id=" + id); }}
/// 無効な状態遷移が行われた場合の例外。public class InvalidFeatureATransitionException extends BusinessRuleException { public InvalidFeatureATransitionException(String message) { super(message); }}ドメイン例外には HTTP ステータスコードを含めない。
例外メッセージは、利用者または開発者が原因を理解できる内容にする。
避ける例:
throw new RuntimeException("error");推奨例:
throw new FeatureANotFoundException(id.value());17. 共通エラーレスポンス規約(RFC 9457 Problem Details)
Section titled “17. 共通エラーレスポンス規約(RFC 9457 Problem Details)”API エラー時は RFC 9457 Problem Details 形式でレスポンスを返却する。
{ "type": "https://example.com/problems/not-found", "title": "リソースが見つかりません", "status": 404, "detail": "指定された ID が存在しません。id=123", "instance": "/api/v1/feature-a/entities/123"}バリデーションエラー(422)には errors 配列を含める。
{ "type": "https://example.com/problems/validation-error", "title": "入力値が不正です", "status": 422, "errors": [ { "field": "title", "message": "タイトルは必須です" } ]}ProblemDetail を各所で手組みせず、GlobalExceptionHandler の buildProblem ヘルパーに集約する。
18. 共通例外ハンドラ規約
Section titled “18. 共通例外ハンドラ規約”例外は @RestControllerAdvice(GlobalExceptionHandler)で一元的に処理する。
@RestControllerAdvicepublic class GlobalExceptionHandler {
@ExceptionHandler(FeatureANotFoundException.class) public ResponseEntity<ProblemDetail> handleFeatureANotFoundException( FeatureANotFoundException ex, HttpServletRequest request) { return buildProblem(HttpStatus.NOT_FOUND, ProblemTypes.NOT_FOUND, ex.getMessage(), request); }
@ExceptionHandler(BusinessRuleException.class) public ResponseEntity<ProblemDetail> handleBusinessRuleException( BusinessRuleException ex, HttpServletRequest request) { return buildProblem(HttpStatus.UNPROCESSABLE_ENTITY, ProblemTypes.BUSINESS_RULE_VIOLATION, ex.getMessage(), request); }
private ResponseEntity<ProblemDetail> buildProblem( HttpStatus status, URI type, String detail, HttpServletRequest request) { // 共通 buildProblem ヘルパーの実装 ... }}Handler ごとに同じような try-catch を書かない。
19. バリデーション規約
Section titled “19. バリデーション規約”19.1 OAS 由来のバリデーション(生成 DTO)
Section titled “19.1 OAS 由来のバリデーション(生成 DTO)”OAS に記述した制約(minLength・maxLength・pattern・required 等)が生成 DTO に @NotNull・@Size・@Pattern として付与される。
Handler メソッドの @Valid でこれを起動する。
# OAS で制約を定義する例title: type: string minLength: 1 maxLength: 255 pattern: '.*\S.*'生成 DTO に手動で @NotBlank 等を追加しない(手編集禁止)。
19.2 意味的 parse(Handler 層)
Section titled “19.2 意味的 parse(Handler 層)”基本型 → ドメイン値オブジェクトへの変換は Handler 層で行う。
@Overridepublic ResponseEntity<FeatureAResponse> createFeatureA( CreateFeatureARequest request) { // LocalDate → DueDate などの意味的 parse var dueDate = DueDate.from(request.getDueDate()); var featureA = createFeatureAUseCase.execute( new CreateFeatureACommand(request.getTitle(), dueDate)); return ResponseEntity.status(HttpStatus.CREATED) .body(FeatureAResponseMapper.from(featureA));}19.3 Domain のスマートコンストラクタ
Section titled “19.3 Domain のスマートコンストラクタ”Domain 値オブジェクトのコンストラクタ内で意味的制約を検証する(アーキテクチャ方針 6.2 参照)。
Bean Validation アノテーション(@NotNull・@Min 等)はドメインオブジェクトに付与しない。
19.4 UseCase 層での業務チェック
Section titled “19.4 UseCase 層での業務チェック”DB 参照が必要な整合性チェックは UseCase 層で行う。
例:
public FeatureA execute(CreateFeatureACommand command) { return transactionManager.execute(() -> { // 重複確認(DB 参照が必要なため UseCase で実施) if (featureARepository.existsByTitle(command.title())) { throw new FeatureADuplicateException("既に登録されています。"); } return featureARepository.save(FeatureA.create(command.title())); });}20. トランザクション規約
Section titled “20. トランザクション規約”@Transactional は使用しない。
domain.port.TransactionManager を使用してトランザクションを制御する。
更新系・参照系ともに UseCase の execute メソッド内で TransactionManager を使う。
更新系:
return transactionManager.execute(() -> { var featureA = repository.findByIdForUpdate(id) .orElseThrow(() -> new FeatureANotFoundException(id.value())); return repository.save(featureA.doSomething());});参照系:
return transactionManager.execute( TransactionOptions.forReadOnly(), () -> repository.findAll());Handler 層・Infrastructure 層では原則として直接トランザクション制御を行わない。
21. Optional 利用規約
Section titled “21. Optional 利用規約”Repository の戻り値が存在しない可能性がある場合は Optional を使用する。
推奨:
FeatureA featureA = featureARepository.findById(id) .orElseThrow(() -> new FeatureANotFoundException(id.value()));避ける例:
Optional<FeatureA> optFeatureA = featureARepository.findById(id);
if (optFeatureA.isPresent()) { return optFeatureA.get();}
return null;原則として Optional.get() を直接使用しない。
22. null 取り扱い規約
Section titled “22. null 取り扱い規約”全パッケージの package-info.java に JSpecify の @NullMarked を付与する。
null 許容箇所のみ @Nullable を付与する。
// package-info.java の例@NullMarkedpackage com.<org>.<feature>.domain.model;
import org.jspecify.annotations.NullMarked;// @Nullable の使用例@Nullable OffsetDateTime createdAt()戻り値として安易に null を返さない。対象データが存在しない場合は、空のコレクションや Optional を返すことを基本とする。業務ロジック上「対象データが必ず存在すべき」と判断する場合に限り例外を送出する。
避ける例:
public FeatureA findById(FeatureAId id) { return null;}推奨例(業務上必ず存在すべきケース):
public FeatureA findById(FeatureAId id) { return featureARepository.findById(id) .orElseThrow(() -> new FeatureANotFoundException(id.value()));}23. コレクション利用規約
Section titled “23. コレクション利用規約”戻り値のコレクションは null ではなく空リストを返す。
推奨:
return entities.stream() .map(FeatureAResponseMapper::from) .toList();避ける例:
if (entities.isEmpty()) { return null;}24. 日時実装規約
Section titled “24. 日時実装規約”日時は以下の型を使用する。
| 用途 | Java 型 |
|---|---|
| 日付 | LocalDate |
| 日時 | OffsetDateTime |
| 時刻 | LocalTime |
外部に出す日時(API レスポンス)は UTC 日時とする。
現在日時の取得は UTC を明示する。
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);UseCase 層では Clock をコンストラクタ注入することを基本とする。Clock を注入することでテスト時に任意の時刻を注入でき、テスタビリティが向上する。
// UseCase での Clock 注入例private final Clock clock;
public CreateFeatureAUseCase(FeatureARepository repository, TransactionManager tx, Clock clock) { this.repository = repository; this.transactionManager = tx; this.clock = clock;}
OffsetDateTime now = OffsetDateTime.now(clock);タイムゾーンの扱いは暗黙的な環境依存を避け、明示的に指定する。
25. ログ出力規約
Section titled “25. ログ出力規約”ログは SLF4J を使用する。
private static final Logger log = LoggerFactory.getLogger(CreateFeatureAUseCase.class);Lombok は使用しない(第 26 節参照)。LoggerFactory.getLogger を直接使用する。
セキュリティイベント(認証失敗・権限エラー等)を記録する。
UseCase・Domain には Observability の計装コード(io.micrometer・io.opentelemetry)を書かない。
25.1 ログレベル
Section titled “25.1 ログレベル”| レベル | 用途 |
|---|---|
| ERROR | システムエラー、処理継続不可 |
| WARN | 業務上の警告、想定内エラー |
| INFO | 処理開始、処理終了、主要な業務イベント |
| DEBUG | 開発時の詳細確認 |
25.2 ログ出力禁止情報
Section titled “25.2 ログ出力禁止情報”以下はログに出力しない。
- パスワード
- アクセストークン
- リフレッシュトークン
- API キー
- 秘密鍵
- 個人情報
- 認証ヘッダ
- Cookie
禁止例:
log.info("Authorization={}", authorizationHeader);log.info("password={}", password);26. Lombok 利用規約
Section titled “26. Lombok 利用規約”本プロジェクトでは Lombok を使用しない。
Java 25 の record を積極的に活用し、ボイラープレートは record の自動生成機能で代替する。
// Lombok 不使用。record を使用する。record FeatureAId(long value) { public FeatureAId { if (value <= 0) throw new InvalidFeatureAIdException(); }}ロガーは SLF4J を直接使用する。
// @Slf4j ではなく直接宣言するprivate static final Logger log = LoggerFactory.getLogger(CreateFeatureAUseCase.class);27. コメント規約
Section titled “27. コメント規約”Javadoc は ///(Markdown コメント)を使用する(Java 25 の機能)。
コメントは「なぜそうしているか」を説明するために使用する。
処理を読めば分かる内容をコメントしない。
避ける例:
// id を取得するlong id = request.getId();推奨例:
// RLS の SET ROLE はトランザクション内で確実に実行する必要があるため、// @Transactional ではなく TransactionManager ポートで制御するreturn transactionManager.execute(() -> { ...});27.1 TODO コメント
Section titled “27.1 TODO コメント”TODO コメントは原則として使用しない。
やむを得ず使用する場合は、理由と対応内容を明記する。
// TODO 認証仕様確定後、ログインユーザー ID を設定するentity.setCreatedBy("system");28. import 規約
Section titled “28. import 規約”未使用 import は禁止する。
ワイルドカード import は禁止する。
禁止:
import java.util.*;推奨:
import java.util.List;import java.util.Optional;29. フォーマット規約
Section titled “29. フォーマット規約”フォーマットは Google Java Format(Spotless)に準拠する。
手動フォーマットではなく ./gradlew spotlessApply を使用する。
インデントは半角スペース 2 つ(Google Java Format 準拠)。
1 行が長くなりすぎる場合は適切に改行する(目安:100 文字以内)。
30. application.yaml 規約
Section titled “30. application.yaml 規約”設定ファイルは application.yaml を基本とする(YAML 形式)。
環境ごとの差分はプロファイル別ファイルに分離する。
application.yamlapplication-local.yamlapplication-dev.yamlapplication-prod.yaml設定例:
spring: datasource: url: ${DB_URL} username: ${DB_USERNAME} password: ${DB_PASSWORD} mybatis: mapper-locations: classpath:mapper/**/*.xml
app: transaction: isolation: DEFAULT timeout: 30
logging: level: root: INFO com.<org>.<feature>: DEBUG31. 機密情報管理規約
Section titled “31. 機密情報管理規約”以下の情報をソースコードや Git 管理対象ファイルに直接記載しない。
- DB パスワード
- API キー
- アクセストークン
- 秘密鍵
- クライアントシークレット
禁止:
spring: datasource: password: mypassword推奨:
spring: datasource: password: ${DB_PASSWORD}32. DI 実装規約
Section titled “32. DI 実装規約”依存性注入はコンストラクタインジェクションを使用する。
32.1 Domain・UseCase の DI
Section titled “32.1 Domain・UseCase の DI”Domain・UseCase には @Component・@Service を付与しない。
config/AppConfig に @Bean を書いて明示的に依存グラフを組み立てる。
推奨:
@Configurationpublic class AppConfig {
@Bean public CreateFeatureAUseCase createFeatureAUseCase( FeatureARepository featureARepository, TransactionManager transactionManager) { return new CreateFeatureAUseCase(featureARepository, transactionManager); }
@Bean public FeatureARepository featureARepository(FeatureAMapper featureAMapper) { return new FeatureARepositoryImpl(featureAMapper); }}禁止:
// 禁止: UseCase に @Service を付ける@Servicepublic class CreateFeatureAUseCase { ... }
// 禁止: Domain に @Component を付ける@Componentpublic class FlightPlan { ... }32.2 Handler・Infrastructure の DI
Section titled “32.2 Handler・Infrastructure の DI”Web 層(@RestController・@RestControllerAdvice・Filter・@Configuration)だけは Spring のライフサイクルに乗せるためアノテーションで登録する。
フィールドインジェクション(@Autowired フィールド注入)は原則使用しない。
禁止:
@Autowiredprivate FeatureARepository featureARepository;推奨:
// コンストラクタインジェクションpublic CreateFeatureAUseCase( FeatureARepository featureARepository, TransactionManager transactionManager) { this.featureARepository = featureARepository; this.transactionManager = transactionManager;}33. DI 対象クラス規約
Section titled “33. DI 対象クラス規約”Spring 管理対象のクラスには適切なアノテーションを付与する。
| 種別 | アノテーション |
|---|---|
| Handler | @RestController |
| Config | @Configuration |
| 共通例外ハンドラ | @RestControllerAdvice |
| Filter | Spring Filter の登録(WebConfig) |
| UseCase | なし(AppConfig の @Bean で登録) |
| Domain | なし(Spring 非依存) |
| Repository 実装 | なし(AppConfig の @Bean で登録) |
| MyBatis Mapper | @Mapper(または @MapperScan で一括登録) |
34. OpenAPI との整合規約
Section titled “34. OpenAPI との整合規約”OpenAPI yaml に定義された以下を実装に反映する(スキーマファースト)。
- path
- HTTP method
- requestBody
- parameters
- responses
- schema
- required
- maxLength / minLength
- format
- pattern
- example
生成コードは手編集しない。OAS を変更したら ./gradlew openApiSyncToSrc で再生成する。
Handler は生成された XxxApi インターフェースを implements する。
35. DB create SQL との整合規約
Section titled “35. DB create SQL との整合規約”DB create SQL に定義された以下を Repository 実装(MyBatis Mapper XML・DB 行レコード)へ反映する。
- テーブル名
- カラム名
- データ型
- nullable
- length
- primary key
- foreign key
- unique 制約
- index
- default 値
DB 行レコードの型や制約は DB create SQL を正とする。
ドメインオブジェクトに ORM アノテーション(@Column 等)を付与しない。
36. API バージョニング規約
Section titled “36. API バージョニング規約”API は URI パス方式でバージョニングする。バージョン番号は整数とする。
内部 API と外部 API はパスレベルで分離する。
具体的なパス設計は機能別方針ドキュメントを参照すること。
37. 優先順位
Section titled “37. 優先順位”複数資料間で矛盾がある場合は、以下の優先順位とする。
- 業務仕様書
- OpenAPI yaml
- DB create SQL
- アーキテクチャ方針.md
- 本コーディング規約.md
- Claude Code の一般判断
判断に迷う場合は、実装内コメントまたは生成結果の説明で仮定を明示すること。
38. テストコード規約
Section titled “38. テストコード規約”テストコードは以下の方針で作成する。
- 正常系を必ず作成する
- 主要な異常系を作成する
- 業務ロジックは UseCase テストで確認する
- Handler は HTTP ステータスとレスポンス形式を確認する
- Repository は MyBatis Mapper の BoundSql および実 DB(Testcontainers)でテストする
- ArchUnit で層依存を自動検査する
39. テストクラス命名規約
Section titled “39. テストクラス命名規約”テストクラス名は対象クラス名 + Test とする。
| 対象クラス | テストクラス |
|---|---|
| CreateFeatureAUseCase | CreateFeatureAUseCaseTest |
| FeatureAHandler | FeatureAHandlerTest |
| FeatureARepositoryImpl | FeatureARepositoryImplTest |
| FeatureAMapper | FeatureAMapperBoundSqlTest |
40. UseCase テスト規約
Section titled “40. UseCase テスト規約”UseCase テストでは JUnit 6 と Mockito を使用する。
TransactionManager には NoopTransactionManager(テスト用実装)を使用する。
@ExtendWith(MockitoExtension.class)class CreateFeatureAUseCaseTest {
@Mock private FeatureARepository featureARepository;
private CreateFeatureAUseCase useCase;
@BeforeEach void setUp() { // NoopTransactionManager: トランザクションなしでアクションを実行する TransactionManager noopTx = new TransactionManager() { @Override public <T> T execute(Supplier<T> action) { return action.get(); } }; useCase = new CreateFeatureAUseCase(featureARepository, noopTx); }
@Test void execute_正常な入力_エンティティを作成して返却する() { var expected = new FeatureA(...); when(featureARepository.save(any())).thenReturn(expected);
var result = useCase.execute(new CreateFeatureACommand("テストデータ"));
assertThat(result).isEqualTo(expected); verify(featureARepository).save(any()); }
@Test void execute_空のタイトル_InvalidFeatureATitleExceptionをスローする() { assertThatThrownBy(() -> useCase.execute(new CreateFeatureACommand(""))) .isInstanceOf(InvalidFeatureATitleException.class); }}状態遷移・競合制御が必要な UseCase テストでは、findByIdForUpdate をスタブする(findById ではない)。
41. Handler テスト規約
Section titled “41. Handler テスト規約”Handler テストでは MockMvc を使用する。
@WebMvcTest(FeatureAHandler.class)class FeatureAHandlerTest {
@Autowired private MockMvc mockMvc;
@MockBean private CreateFeatureAUseCase createFeatureAUseCase;
@Test void createFeatureA_正常終了する() throws Exception { var featureA = new FeatureA(...); when(createFeatureAUseCase.execute(any())).thenReturn(featureA);
mockMvc.perform(post("/api/v1/feature-a/entities") .contentType(MediaType.APPLICATION_JSON) .content(""" {"title": "テストデータ"} """)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id").exists()); }}42. テストメソッド命名規約
Section titled “42. テストメソッド命名規約”テストメソッド名は、テストの意図が明確に分かる名前とする。形式・命名スタイルは開発者に任せる。
43. アサーション規約
Section titled “43. アサーション規約”アサーションには AssertJ を使用することを推奨する。
推奨:
assertThat(featureA.id()).isEqualTo(expectedId);assertThat(featureA.title()).isEqualTo("テストデータ");例外検証:
assertThatThrownBy(() -> useCase.execute(invalidCommand)) .isInstanceOf(FeatureANotFoundException.class) .hasMessageContaining("エンティティが見つかりません");44. ArchUnit 規約
Section titled “44. ArchUnit 規約”ArchUnit によるアーキテクチャテストを必ず作成する。
確認対象:
// Domain が Spring・MyBatis・Infrastructure を参照していないこと// UseCase が HTTP 固有の型を使用していないこと// @Transactional が UseCase・Domain に使用されていないこと// UseCase・Domain が io.micrometer / io.opentelemetry を import していないこと// generated コードが Handler 層からのみ参照されていること層またはパッケージを追加したらルールも確認する。
45. equals / hashCode 規約
Section titled “45. equals / hashCode 規約”Domain Entity に equals / hashCode を実装する場合は注意する。
record を使用する場合は自動生成されるため通常は問題ない。
カスタム実装が必要な場合は以下を確認すること:
- 双方向関連を持つ Entity への安易な付与は避ける
- ID 採番前後で同一性が変わる Entity への注意
- 遅延ロード項目を持つ場合の注意
46. toString 規約
Section titled “46. toString 規約”Domain や DTO に toString を実装する場合は、機密情報を含めない。
// @Override toString で機密情報を除外する@Overridepublic String toString() { return "FeatureA{id=" + id + ", title=" + title + "}";}record を使用する場合はデフォルトの toString で機密情報が出力されないよう注意する。
47. API レスポンス規約
Section titled “47. API レスポンス規約”ドメイン型を API レスポンスとして直接返却しない。
禁止:
@Overridepublic ResponseEntity<FeatureA> getFeatureA(Long id) { return ResponseEntity.ok(featureARepository.findById(id).orElseThrow());}推奨:
@Overridepublic ResponseEntity<FeatureAResponse> getFeatureA(Long id) { var featureA = getFeatureAUseCase.execute(id); return ResponseEntity.ok(FeatureAResponseMapper.from(featureA));}48. HTTP ステータス規約
Section titled “48. HTTP ステータス規約”API の HTTP ステータスは以下を基本とする。
| 処理 | ステータス |
|---|---|
| 取得成功 | 200 OK |
| 登録成功 | 201 Created |
| 更新成功 | 200 OK |
| 削除成功 | 204 No Content |
| 入力エラー(形式・必須) | 400 Bad Request |
| 認証エラー | 401 Unauthorized |
| 認可エラー | 403 Forbidden |
| 対象なし | 404 Not Found |
| 競合 | 409 Conflict |
| ビジネスルール違反 | 422 Unprocessable Entity |
| サーバエラー | 500 Internal Server Error |
HTTP ステータスの設定は Handler 層の責務であり、UseCase・Domain では設定しない。
認証の詳細は AuthenticationSpecification_Common.md を参照すること。