序文
日々の業務において、私たちが引き継いだり保守したりするプロジェクトの多くは、コントローラー、サービス、daoの三層アーキテクチャを採用しており、使用する過程で以下のような多くの問題に遭遇します。
- データ指向モデリング、プロセス指向プログラミング、本当の「オブジェクト指向」は存在しない
- プロセスではなく結果のみに焦点を当てているため、サービス層には多くの場合、プロセス コードとグルー コードでいっぱいの数百行または数千行があり、肥大化して実行中のアカウント、反復的、または論理的に分散しているため、システム内での保守が非常に困難になります。未来。
- コードの結合は深刻で、レイヤー同士が相互に呼び出したり、逆呼び出ししたりするため、全身に影響を及ぼします。
- コードにビジネスを反映することはできず、誰もコメントを書きたくない場合、時間が経つにつれて、誰もコードのビジネス ロジックを理解できなくなり、コードを変更しようとする人もいなくなります。
それで、良い解決策はありますか?今日説明する DDD は良い選択です。
DDD
DDD (ドメイン駆動設計)は、上記の問題を完全に解決します。
- ドメイン指向モデリング、オブジェクト指向プログラミング、コードは現実世界の概念を直接マッピングし、ビジネスに近く、顧客に近くなります。
- ドメイン ロジックは高度に凝集しており、Java 開発原則に準拠しています。
- データベース、キャッシュ、タイマーなどの技術的な詳細の変更は、ビジネス ロジックへの影響が比較的小さく、プラグイン アーキテクチャに非常に適しています。
- コードは読みやすく保守しやすく、その後の拡張や移植などのサポートが向上し、階層化がより科学的になっています。
DDD の概念はオンラインで簡単に見つかるので、ここでは詳しく説明しません。
しかし、インターネット上には DDD に関する記事が数多くありますが、そのほとんどは理論的な知識であり、その紹介は戦略デザイン、戦術デザイン、コア ドメイン、サポート ドメイン、価値オブジェクト、エンティティ、集約などのいくつかの用語にすぎません。 . ただし、実際の実装にはあまり役に立ちません。ご協力ありがとうございます。SpringBoot に DDD を適用するための実装計画を示します。
実行計画
1. コードの階層化
コードの階層化
- ユーザーインターフェイス層:写真のAPIパッケージ(つまりコントローラー層、コントローラーのサフィックスが長すぎると思います...)
- アプリケーション層:ここではコマンドモードを使用しており、読み出しと書き込みを2つのパッケージ(コマンド、クエリ)に分けていますが、コマンドモードを使用しない場合は1つのサービスパッケージにまとめることも可能です。
- ドメイン層: JPA を使用したドメイン パッケージ (DDD を適切にサポート)
- インフラストラクチャ層: infra パッケージ、その他すべてのパブリック コンポーネントがここに配置されます。DIP 依存関係の反転が使用される場合、実装クラスもここに配置されます。
- モデル モデル: モデル パッケージ。異なるレイヤー間で転送されるオブジェクトを保存するために使用されます。これらのオブジェクトをさまざまな場所に配置しようとしましたが、最終的には 1 つのパッケージの下に配置する方がよいことがわかりました (サービス間で呼び出すときにオブジェクトを共有しやすくするため)。
2. 階層関係とモデルの転送
関係の階層化と呼び出し
3. レイヤリングの詳細説明
- APIパッケージ(コントローラー)
@Tag(name = "用户", description = "用户")
@RestController
@RequestMapping(value = "/api/sys-user")
public class SysUserApi extends BaseApi {
@ApiResult
@Operation(summary = "根据ID查询用户")
@GetMapping("/{id}")
public SysUserVo get(@PathVariable Long id) {
return queryExecutor.execute(new SysUserByIdQry(id));
}
@Pagination(total = true)
@ApiResult
@Operation(summary = "分页查询用户")
@GetMapping
public List<SysUserVo> getList(SysUserQo sysUserQo) {
return queryExecutor.execute(new SysUserListQry(sysUserQo));
}
@ApiResult
@Operation(summary = "新增用户")
@PostMapping
public void save(@Valid @RequestBody SysUserDto sysUserDto) {
commandExecutor.execute(new SysUserCommonCmd(sysUserDto));
}
}
2 つのコマンド実行クラス、queryExecutor と commandExecutor は BaseApi にカプセル化されており、@Autowired が異なるサービスを導入しなくても、アプリケーション層を呼び出すときに異なるコマンドを実行できます。
@ApiResult がこのカスタム アノテーションを追加すると、返された結果は均一にカプセル化されます。
@Pagination このカスタム アノテーションを追加すると、ページング パラメータがスレッド変数に自動的に保存されます。ページング パラメータは後続のクエリでも自動的に取得されます。ページング情報は、返された結果が統合されてパッケージ化されるときにも追加されます。
Qo はクエリ パラメータ オブジェクト、Dto は追加、削除、変更などのコマンド パラメータ オブジェクト、返されるオブジェクトは Vo です。ここで注意すべき点は、Entity をこの層に公開してはならず、Vo に変換してから変換する必要があることです。戻ってきた。
この層では、各メソッドはほぼコマンドを実行するステートメントの行であり、通常、ビジネス ロジックは実行されません (もちろん特殊な場合もあります)。
- コマンドパッケージ
@AllArgsConstructor
public class SysDeptAddCmd implements Command<Void> {
private SysDeptDto sysDeptDto;
@Override
public Void execute(Executor executor) {
// 获取命令的接收者:领域服务
SysDeptManager receiver = executor.getReceiver(SysDeptManager.class);
// 对象模型转换,由DTO转为Entity,使用了MapStruct
SysDept sysDept = SysDeptMapper.INSTANCE.toSysDept(sysDeptDto);
// 使用JPA保存
receiver.save(sysDept);
return null;
}
}
作業のオーガナイザーとして非常に薄い層のコマンドを追加、削除、および変更します。ビジネス ロジックはほとんどなく、ドメイン サービスと輻輳オブジェクト メソッドを呼び出します。
コマンド モード、カスタム コマンド インターフェイスを実装し、ジェネリックは戻り値です
プロパティとコンストラクターを介してパラメーターを受け取ります (lombok アノテーションを使用)
コマンドには実行メソッドが 1 つだけあります。欠点は、多数のコマンド クラスが生成されることです。各クラスは前のサービス クラスのメソッドに相当しますが、これは単一責任の原則に準拠しています。
executor.getRecerver メソッドを通じてドメイン サービス (マネージャー) を取得します。
DTO はドメイン層に侵入することはなく、まず DTO から Entity に変換する必要があります (ここで使用する MapStruct は変換方法であり、詳細は後述します)。
- クエリパッケージ
@AllArgsConstructor
public class SysDeptByIdQry extends CommonQry<SysDeptVo> {
private Long id;
@Override
public SysDeptVo execute(Executor executor) {
if (id == null) {
throw new BusinessException("部门ID不能为空");
}
QSysDept sysDept = QSysDept.sysDept;
return queryFactory.select(this.fields())
.from(sysDept)
.where(sysDept.deleted.eq(false), sysDept.id.eq(id))
.fetchOne();
}
/**
* 部门VO映射
*
* @return QBean<SysDeptVo>
*/
public static QBean<SysDeptVo> fields() {
QSysDept sysDept = QSysDept.sysDept;
return Projections.fields(
SysDeptVo.class,
sysDept.deptName,
sysDept.orderNum,
sysDept.id
);
}
}
コマンド モード。カスタム CommonQry 基本クラスを継承し (このクラスは、QueryDSL の queryFactory クラスを参照し、ページング メソッドをカプセル化するカスタム Command インターフェイスも実装します)、ジェネリック型が戻り値です。
query パッケージの query コマンドはコマンド パッケージのコマンドとほぼ同じですが、唯一の違いは、query コマンドは理論的にはドメイン層を呼び出さずにデータベースに直接クエリを実行することです。
JPA は複雑なクエリには使いにくいため、QueryDSL (詳細は後述) を使用することを強くお勧めします。図は簡単な使用例です。
- ドメインパッケージ
多くのクラスがあり、コード部分を 1 つずつリストすることはできません。
データベーステーブルはEntityクラスによって自動生成され、Entityクラスのみが維持されます(データベースをシールドします)
Entityの設計時には@OneToMany、@OneToOneなどのアノテーションを実際の業務に合わせて柔軟に利用してください(集約ルートの概念)
集約ルートは大きすぎてはなりません。80% の場合、集約ルートには 1 つのエンティティのみが含まれます (大規模な集約ルートに設計しすぎないでください)。
貧血モデルは使わず、オブジェクト指向にする オブジェクトに属するメソッドはオブジェクトの中に置くべき ただし、リポジトリクラスをオブジェクトに導入することは推奨しない データベースを操作する必要のあるメソッドは、ドメインサービスマネージャー。
ビジネス ロジックは可能な限りドメイン サービス (マネージャー) に記述し、アプリケーション層の呼び出しに対してさまざまなメソッドを継続的に抽出および抽象化する必要があります。
ドメイン イベントを適切に使用すると、JPA は Entity で @DomainEvents アノテーションを使用してドメイン イベントを送信できます
経験
- DDD を通じて、ビジネスをより徹底的に理解することができ、作成するコードは顧客のビジネス要求をより適切に伝えることができます。
- 低結合で、単一責任、オープンとクローズ、カプセル化、継承、およびポリモーフィズムの原則に準拠したコードを作成できるのは非常に満足です。
- 従来のアーキテクチャと比較して、初期段階のコードの量が多く、開発者は初期段階により多くの投資を行います。
- 合理的な分野分割と合理的なエンティティの設計
- 多数の DTO、VO、およびその他のデータ オブジェクト
- 多数のデータオブジェクト変換メソッド
- 多数のコマンドクラス
- ...
ただし、特に単純な機能でない限り、ある程度複雑なシステムの場合、こうした事前の取り組みは依然として価値があります。
まとめ
以上、私の DDD の理解と実践を簡単に紹介し、実際のコードを通して SpringBoot に DDD を適用する方法を示しました。
OpenAI、ChatGPTを全ユーザーに無料公開 音声 プログラマーがETC残高を改ざんし年間260万元以上を横領 Spring Boot 3.2.0が正式リリース Google従業員が退社後の偉い人を批判 に深く関与Flutter プロジェクトと策定された HTML 関連標準 Microsoft Copilot Web AI が 12 月 1 日に正式にリリースされ、中国 Microsoft のオープンソース ターミナル チャットをサポート Rust Web フレームワーク Rocket が v0.5 をリリース: 非同期、SSE、WebSocket などを サポートRedis は、純粋な C 言語コードを使用して Telegram Bot フレームワークを実装します 。オープンソース プロジェクトのメンテナーであれば、「この種の応答にどこまで耐えることができますか?」という問題に遭遇します。 PHP 8.3 GAこの記事は、複数の記事を公開するブログOpenWriteによって公開されています。