今回の記事では、ビジネス設計からソースレベルのパッケージ設計およびコントローラー設計まで、ビジネス観点からの全体的設計方法を紹介します。
WhaTap APMの新機能である**「ビジネスダッシュボード」**について説明する前に、ITシステムをどのように設計及び開発したら、その後の管理が容易になるかについてまず話します。内容はビジネス設計においてRESTful APIをどのように設計すればよいか、なぜそのように設計する必要があるのか、そしてその設計によるメリットの順で説明します。
ビジネス設計においてURLの1~2デプスを基準に構造化することは、実務で広く使われている合理的な手法です。RESTful API設計においても、この手法はビジネスドメイン中心の構造に自然に繋がります。
開発者や運用者はURLを見ただけで当該業務が簡単に区別できるため、トランザクション分析や障害分析の際に問題を迅速に把握できます。
bash
コピー編集
GET /account → マイアカウント一覧の照会
GET /account/{accountId} → 特定アカウントの詳細
POST /account → アカウント開設
DELETE /account/{accountId} → アカウント解除
POST /auth/login → ログイン
POST /auth/logout → ログアウト
POST /auth/register → 会員登録
GET /cards → 顧客カードの一覧
POST /cards → カード申込み
PUT /cards/{cardId}/activate → カード活性化
DELETE /cards/{cardId} → カード解除
bash
コピー編集
GET /search/account → マイアカウント一覧の照会
GET /account/detail/{accountId} → 特定アカウントの詳細
POST /save/account → アカウント開設
DELETE /account/drop/{accountId} → アカウント解除
POST /auth/login → ログイン
POST /logout → ログアウト
POST /register → 会員登録
GET /list/cards → 顧客カードの一覧
POST /order/cards → カード申込み
PUT /card/{cardId}/activate → カード活性化
DELETE /drop/card/{cardId} → カード解除
上記の二つの例を比較すると、前の構造はドメインと機能が明確に区別されており、直感的に理解できます。一方、後ろの構造はどのリクエストがどのドメインに属しているのかをすぐに把握することが難しいです。
自分も過去に時間に追われて急いで開発した結果、その後のメンテナンス過程で問題の原因を見つけるのにより多くの時間がかかった経験があります。開発初期の設計がどれほど重要であるかをぜひ覚えておいてください。
機能名とドメイン名を混用すると、メンテナンスが難しくなります。
sql
コピー編集
/createOrder → 機能: create / ドメイン: order
/deleteOrder → 機能: delete / ドメイン: order
/orderDelete → ドメイン: order / 機能: delete
推奨される手法は次のようにドメイン優先構造です。
sql
コピー編集
/order/create → ドメイン: order / 機能: create
/order/delete → ドメイン: order / 機能: delete
これにより、どのリソースにどのアクションが適用されるかをひと目で把握できます。
pgsql
コピー編集
com.whatap.api
├── account
│ ├── controller
│ ├── service
│ ├── dto
│ └── domain
├── customers
├── cards
├── auth
└── common
URL構造とパッケージ構造を1:1でマッピングすると、メンテナンスがとても楽になります。例えばURL構造とパッケージ構造を1:1でマッピングすると、メンテナンスがとても楽になります。例えば、/account/100 を呼び出した際に500エラーが発生した場合、account ドメインのAccountControllerをすぐに見つけて確認できます。
java
コピー編集
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping
public ResponseEntity<List<AccountResponse>> getAccount() { ... }
@GetMapping("/{accountId}")
public ResponseEntity<AccountDetailResponse> getAccount(@PathVariable Long accountId) { ... }
@PostMapping
public ResponseEntity<Void> openAccount(@RequestBody AccountCreateRequest request) { ... }
@DeleteMapping("/{accountId}")
public ResponseEntity<Void> closeAccount(@PathVariable Long accountId) { ... }
}
java
コピー編集
@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;
...
}
bash
コピー編集
POST /account
↓
AccountController.createAccount()
↓
AccountService.createAccount()
↓
AccountRepository.save()
↓
Account Entity 생성