DDD階層化アーキテクチャのベストプラクティス

それがまだ単一のアプリケーションにある場合、それは階層化アーキテクチャです。私たちは3層アーキテクチャを最も使用します。今がマイクロサービスの時代です。クリーンアーキテクチャ、CQRS(コマンドクエリ分離)、六角形アーキテクチャなど、一般的に使用されるマイクロサービスアーキテクチャモデルがいくつかあります。各アーキテクチャモデルには独自のアプリケーションシナリオがありますが、そのコアは「高凝集度と低結合度」の原則です。ドメイン駆動設計(DDD)の概念を使用して、日々加速するビジネスの変化がアーキテクチャに与える影響に対処することで、アーキテクチャの境界が明確になり、それぞれが独自の役割を果たします。これは、次の設計哲学にも沿っています。マイクロサービスアーキテクチャ。ドメイン駆動設計(DDD)の概念に基づく階層化アーキテクチャは、マイクロサービスアーキテクチャの実践のためのベストプラクティスの方法になりました。

1.DDD階層化アーキテクチャとは

1.従来の3層アーキテクチャ

DDDの階層化アーキテクチャを理解するには、まず従来の3層アーキテクチャを理解します。

従来の3層アーキテクチャプロセス:

  • 最初のステップは、データベースの設計、データテーブルの作成方法、テーブル間の関係の設計方法を検討することです。
  • 2番目のステップは、ORMフレームワークの選択や、SQL操作のスプライシングなど、データアクセス層を構築することです。
  • 3番目のステップはビジネスロジックの実現です。最初にデータベースを設計したので、私たちの思考全体はデータベースを中心に展開し、データベースにデータを正しく書き込むためにデータを書き込む方法を考えます。現時点では、標準的な方法です。オブジェクト指向とデカップリングについてはあまり考慮されていません。そのようなコードは、当然、日常のメンテナンスではますます困難になっています。
  • 4番目のステップは、主にユーザー向けのレイヤーの出力を表します

2.DDD階層化アーキテクチャ

高結合の問題を解決し、将来のシステム変更に簡単に対処するために、ドメイン駆動設計を使用してアーキテクチャを設計するという概念を提案しました。

この段落は、OuChuangxinの「DDD実践コース」の「07 | DDD階層化アーキテクチャ:レイヤー間の依存関係を効果的に削減する」を読んだ後の考えを部分的に要約しています。

1)ドメインレイヤー

まず、データベースの問題を脇に置いて、ビジネスロジックから始めて、設計時にデータベースの実装を考慮しないようにしましょう前のビジネスロジック層(BLL)をドメイン層とアプリケーション層に分割します。

ドメイン層は、実際のビジネスのロジックの変更を反映して、ビジネスオブジェクトのビジネスロジックの実現に焦点を合わせています。ビジネスコンセプト、ビジネスステータス、ビジネスルールを表現するために使用されます。ビジネス分析については、「ドメイン駆動型設計を使用したビジネス分析」を参照してください。

2)アプリケーション層

アプリケーション層は、ドメイン層に依存するドメイン層の上位層であり、集約の調整とオーケストレーションであり、原則としてビジネスロジックは含まれていません。これは、より粗いクロージャーを備えたフロントエンドインターフェイスのサポートを提供します。上位レベルの呼び出しを提供することに加えて、イベントとメッセージのサブスクリプションを含めることもできます。

3)ユーザーインターフェイスレイヤー

ユーザーインターフェイスレイヤーは、ユーザーアクセス用のデータ入力インターフェイスに面しており、さまざまなシナリオに応じてさまざまなユーザーインターフェイスの実装を提供できます。Web指向のサービスはhttprestfulモードで提供でき、セキュリティ認証、権限検証、ロギングなどの機能を追加できます。マイクロサービス指向のサービスはRPCモードで提供でき、電流制限や融合などの機能は次のようになります。追加されました。

4)インフラストラクチャ層

インフラストラクチャ層はデータアウトバウンドインターフェイスであり、データ呼び出しの技術的な詳細をカプセル化します。他の層にサービスを提供できますが、結合の問題を解決するために、依存性逆転の原則が採用されています。他のレイヤーはインフラストラクチャのインターフェースのみに依存し、特定の実装から分離されています。

2、DDDレイヤードコードの実装

1.構造モデル

2.ディレクトリ構造

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── fun
    │   │       └── barryhome
    │   │           └── ddd
    │   │               ├── WalletApplication.java
    │   │               ├── application
    │   │               │   ├── TradeEventProcessor.java
    │   │               │   ├── TradeMQReceiver.java
    │   │               │   └── TradeManager.java
    │   │               ├── constant
    │   │               │   └── MessageConstant.java
    │   │               ├── controller
    │   │               │   ├── TradeController.java
    │   │               │   ├── WalletController.java
    │   │               │   └── dto
    │   │               │       └── TradeDTO.java
    │   │               ├── domain
    │   │               │   ├── TradeService.java
    │   │               │   ├── TradeServiceImpl.java
    │   │               │   ├── enums
    │   │               │   │   ├── InOutFlag.java
    │   │               │   │   ├── TradeStatus.java
    │   │               │   │   ├── TradeType.java
    │   │               │   │   └── WalletStatus.java
    │   │               │   ├── event
    │   │               │   │   └── TradeEvent.java
    │   │               │   ├── model
    │   │               │   │   ├── BaseEntity.java
    │   │               │   │   ├── TradeRecord.java
    │   │               │   │   └── Wallet.java
    │   │               │   └── repository
    │   │               │       ├── TradeRepository.java
    │   │               │       └── WalletRepository.java
    │   │               └── infrastructure
    │   │                   ├── TradeRepositoryImpl.java
    │   │                   ├── WalletRepositoryImpl.java
    │   │                   ├── cache
    │   │                   │   └── Redis.java
    │   │                   ├── client
    │   │                   │   ├── AuthFeignClient.java
    │   │                   │   └── LocalAuthClient.java
    │   │                   ├── jpa
    │   │                   │   ├── JpaTradeRepository.java
    │   │                   │   └── JpaWalletRepository.java
    │   │                   └── mq
    │   │                       └── RabbitMQSender.java
    │   └── resources
    │       ├── application.properties
    │       └── rabbitmq-spring.xml
    └── test
        └── java



この構造は、すべてのレイヤーが同じモジュール内にある単一のマイクロサービスの単純な構造です。

大規模なプロジェクト開発の過程で、コアモジュールの典拠コントロールまたはより良い柔軟性を実現するために、構造を適切に調整することができます。「デジタルウォレットシステム」システム構造を参照してください。

3.ドメインレイヤー(ドメイン)の実現

ビジネス分析(「ドメイン駆動設計を使用したビジネスの分析」)の後、コードの記述を開始します。1つ目は、ドメインレイヤーの記述、ドメインオブジェクトの作成、およびドメインサービスインターフェイスです。

1)ドメインオブジェクト

ここでのドメインオブジェクトには、エンティティオブジェクトと値オブジェクトが含まれます。

エンティティオブジェクト:一意の識別子を持ち、単独で存在でき、変更できるオブジェクト

値オブジェクト:単独では存在できない、または論理レベルで単独で存在する、意味がなく不変のオブジェクト

集約:外部全体としての複数のオブジェクトのコレクション

アグリゲーションルート:外部アクセス操作が提供されるビジネスオペレーション全体を表すことができるアグリゲーション内のエンティティオブジェクト。アグリゲーション内のデータの整合性を維持し、アグリゲーション内のオブジェクトのマネージャーです。

コード例:

// 交易
public class TradeRecord extends BaseEntity {
    /**
     * 交易号
     */
    @Column(unique = true)
    private String tradeNumber;
    /**
     * 交易金额
     */
    private BigDecimal tradeAmount;
    /**
     * 交易类型
     */
    @Enumerated(EnumType.STRING)
    private TradeType tradeType;
    /**
     * 交易余额
     */
    private BigDecimal balance;
    /**
     * 钱包
     */
    @ManyToOne
    private Wallet wallet;

    /**
     * 交易状态
     */
    @Enumerated(EnumType.STRING)
    private TradeStatus tradeStatus;

  	@DomainEvents
    public List<Object> domainEvents() {
        return Collections.singletonList(new TradeEvent(this));
    }
}

// 钱包
public class Wallet extends BaseEntity {

    /**
     * 钱包ID
     */
    @Id
    private String walletId;
    /**
     * 密码
     */
    private String password;
    /**
     * 状态
     */
    @Enumerated(EnumType.STRING)
    private WalletStatus walletStatus = WalletStatus.AVAILABLE;
    /**
     * 用户Id
     */
    private Integer userId;
    /**
     * 余额
     */
    private BigDecimal balance = BigDecimal.ZERO;

}



ウォレットトランザクションの例のシステム設計から、リチャージ、メッセージなどのウォレットの操作は、トランザクションオブジェクトを介してウォレット残高の変更を駆動します。

  • トランザクションオブジェクトウォレットオブジェクトはどちらもエンティティオブジェクトであり、集約関係を形成しますトランザクションオブジェクトは、ウォレットトランザクションビジネスモデルの集約ルートであり、外部呼び出しサービスを提供するための集約を表します。
  • トランザクションオブジェクトウォレットオブジェクト(@ManyToOne)の間1対多の関係を分析した後、JPAがORMアーキテクチャとして使用されます。JPAのその他のプラクティスについては、>>を参照してください。

ここでのドメインモデリングは、シンプルな構造、単一責任、優れた分離性を備えているが、オブジェクト指向の設計アイデアが欠けている貧血モデルを使用しています。ドメインモデリングについては、「ドメインモデリングの貧血モデルと輻輳モデル」を参照してください。

  • domainEvents()は、ドメインイベント公開の実装です。この機能は、トランザクションオブジェクトのデータ操作によってイベントの公開がトリガーされ、イベントサブスクリプションと連携してイベント駆動型設計モデル実装することです。もちろん、その他実装も可能です

2)ドメインサービス

/**
 * Created on 2020/9/7 11:40 上午
 *
 * @author barry
 * Description: 交易服务
 */
public interface TradeService {

    /**
     * 充值
     *
     * @param tradeRecord
     * @return
     */
    TradeRecord recharge(TradeRecord tradeRecord);

    /**
     * 消费
     *
     * @param tradeRecord
     * @return
     */
    TradeRecord consume(TradeRecord tradeRecord);
}



最初にサービスインターフェイスを定義します。インターフェイスの定義は、実際のビジネスの操作に従う必要があります。追加、削除、および変更を設計および定義するためにプログラムロジックまたはデータベースロジックを使用しないでください。

  • 主な考え方は、トランザクションオブジェクトが外部で提供できるサービスです。このサービスの定義は、きめが細かく非常にまとまりがあります。特定のコード実装レベルのメソッドを定義しないでください。
  • インターフェイスの入力パラメータと出力パラメータは、さまざまなシーンの変更と完全に互換性のある、可能な限りオブジェクトの形式で検討する必要があります。
  • フロントエンドに必要な複雑なクエリメソッドはここでは定義されていません。通常、クエリはドメインサービスではなく、データの変更はなく、個別に処理できます。
  • ドメインサービスの実現は主に論理的な実現に焦点を合わせており、キャッシュの実現、データベースの実現、リモート呼び出しなどの技術的な基本コードを含めることはできません。

3)インフラストラクチャインターフェイス

public interface TradeRepository {
    /**
     * 保存
     * @param tradeRecord
     * @return
     */
    TradeRecord save(TradeRecord tradeRecord);

    /**
     * 查询订单
     * @param tradeNumber
     * @return
     */
    TradeRecord findByTradeNumber(String tradeNumber);

    /**
     * 发送MQ事件消息
     * @param tradeEvent
     */
    void sendMQEvent(TradeEvent tradeEvent);

    /**
     * 获取所有
     * @return
     */
    List<TradeRecord> findAll();
}



  • インフラストラクチャインターフェイスをドメインレイヤーに配置する主な目的は、ドメインレイヤーのインフラストラクチャレイヤーへの依存を減らすことです。
  • インターフェイスの設計は、アセンブルされたSQLをパラメーターとして使用できないなど、実装の技術的な詳細を公開することではありません。

4.アプリケーション層の実装(アプリケーション)

// 交易服务
@Component
public class TradeManager {

    private final TradeService tradeService;
    public TradeManager(TradeService tradeService) {
        this.tradeService = tradeService;
    }


    // 充值
    @Transactional(rollbackFor = Exception.class)
    public TradeRecord recharge(TradeRecord tradeRecord) {
        return tradeService.recharge(tradeRecord);
    }


     // 消费
    @Transactional(rollbackFor = Exception.class)
    public TradeRecord consume(TradeRecord tradeRecord) {
        return tradeService.consume(tradeRecord);
    }
}

// 交易事件订阅
@Component
public class TradeEventProcessor {

    @Autowired
    private TradeRepository tradeRepository;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "# tradeEvent.tradeStatus.name() == 'SUCCEED'")
    public void TradeSucceed(TradeEvent tradeEvent) {
        tradeRepository.sendMQEvent(tradeEvent);
    }
}

// 交易消息订阅
@Component
public class TradeMQReceiver {

    @RabbitListener(queues = "ddd-trade-succeed")
    public void receiveTradeMessage(TradeEvent tradeEvent){
        System.err.println("========MQ Receiver============");
        System.err.println(tradeEvent);
    }
}



アプリケーションサービス

  • アプリケーション層は非常に薄い層であり、主にドメインサービスの呼び出しと結合に使用され、ビジネスロジックを含めることはできません。
  • 少量のプロセスパラメータの判断を含めることができます
  • 複数のドメインサービスを組み合わせた操作呼び出しである可能性があるため、アトミック性の要件がある場合は、** @ Transactional **トランザクション制御を追加できます。

イベントサブスクリプション

  • イベントサブスクリプションは、プロセス内の複数のフィールドでの操作の連携と分離を実現する方法であり、プロセス内の後続のすべての操作のアクセスポイントでもあります。
  • アプリケーションサービスの組み合わせ操作とは異なり、シーンのニーズに応じて組み合わせを増減できますが、イベントサブスクリプション後の操作は、主に論理的な整合性要件を満たすために比較的固められています

TransactionPhase.AFTER_COMMIT構成は、前の操作トランザクションが完了した後に呼び出されるため、前の操作に対する後続の操作の影響が軽減されます。

  • イベントサブスクリプションには複数のメッセージ本文が含まれる場合があります。管理の便宜のために、それらを1つのクラスで処理するのが最適です。
  • MQメッセージの公開は、通常、イベントサブスクリプションに配置されます

ニュース購読

  • メッセージサブスクリプションは、複数のマイクロサービス間のコラボレーションとデカップリングのワンステップ実装です
  • メッセージ本文は、オブジェクトの不均一性によって引き起こされる処理の難しさを軽減するために、可能な限り均一なオブジェクトパッケージで送信されます。

5.インフラストラクチャ層(インフラストラクチャ)

@Repository
public class TradeRepositoryImpl implements TradeRepository {

    private final JpaTradeRepository jpaTradeRepository;
    private final RabbitMQSender rabbitMQSender;
    private final Redis redis;

    public TradeRepositoryImpl(JpaTradeRepository jpaTradeRepository, RabbitMQSender rabbitMQSender, Redis redis) {
        this.jpaTradeRepository = jpaTradeRepository;
        this.rabbitMQSender = rabbitMQSender;
        this.redis = redis;
    }

    @Override
    public TradeRecord save(TradeRecord tradeRecord) {
        return jpaTradeRepository.save(tradeRecord);
    }

    /**
     * 查询订单
     */
    @Override
    public TradeRecord findByTradeNumber(String tradeNumber) {
        TradeRecord tradeRecord = redis.getTrade(tradeNumber);
        if (tradeRecord == null){
            tradeRecord = jpaTradeRepository.findFirstByTradeNumber(tradeNumber);
            // 缓存
            redis.cacheTrade(tradeRecord);
        }

        return tradeRecord;
    }

    /**
     * 发送事件消息
     * @param tradeEvent
     */
    @Override
    public void sendMQEvent(TradeEvent tradeEvent) {
        // 发送消息
        rabbitMQSender.sendMQTradeEvent(tradeEvent);
    }

    /**
     * 获取所有
     */
    @Override
    public List<TradeRecord> findAll() {
        return jpaTradeRepository.findAll();
    }
}


  • インフラストラクチャ層はデータの出力方向であり、主にデータベース、キャッシュ、メッセージキュー、リモートアクセスなどの技術的な実装が含まれます。
  • 基本設計レイヤーは、技術的な実装の詳細を外部から隠し、大まかなデータ出力サービスを提供します
  • データベース操作:ドメインレイヤーはデータオブジェクトを送信します。データオブジェクトは、データテーブルの実装に応じて分割および実装できます。

6.ユーザーインターフェイスレイヤー(コントローラー)

@RequestMapping("/trade")
@RestController
public class TradeController {

    @Autowired
    private TradeManager tradeManager;

    @Autowired
    private TradeRepository tradeRepository;

    @PostMapping(path = "/recharge")
    public TradeDTO recharge(@RequestBody TradeDTO tradeDTO) {
        return TradeDTO.toDto(tradeManager.recharge(tradeDTO.toEntity()));
    }

    @PostMapping(path = "/consume")
    public TradeDTO consume(@RequestBody TradeDTO tradeDTO) {
        return TradeDTO.toDto(tradeManager.consume(tradeDTO.toEntity()));
    }

    @GetMapping(path = "/{tradeNumber}")
    public TradeDTO findByTradeNumber(@PathVariable("tradeNumber") String tradeNumber){
        return TradeDTO.toDto(tradeRepository.findByTradeNumber(tradeNumber));
    }

}



  • ユーザーインターフェイスレイヤーは、端末にサービスサポートを提供します
  • さまざまなシナリオに応じて、別のモジュールが、サービス間のAPI呼び出しのWebおよびRPGサポートにhttprestfulを提供できます。
  • Web、VOデータ変換のためのID認証および権限検証サービスを提供します
  • API側、DTOデータ変換に電流制限およびヒューズサービスを提供します
  • アプリケーション層からユーザーインターフェイス層へのデータ変換は、アプリケーション層のデータ形式の均一性を確保しながら、さまざまなシナリオの前に要件を変更する場合により便利です。

7.複雑なデータクエリ

以上のことから、複雑なデータクエリの問題はないことがわかります。この問題はビジネスロジック処理を伴わないため、ドメイン層で処理しないでください。

より複雑なデータクエリ要件がある場合は、CQRSモードを使用でき、クエリは単一のモジュールによって処理されます。インフラストラクチャ層で実行できるデータクエリが少ない場合、アプリケーション層でのデータカプセル化、およびユーザーインターフェイス層でのデータ呼び出し

  • JPAは、マルチテーブル関連のデータベースクエリ操作には適していません。他の柔軟なORMアーキテクチャを使用できます。

ビッグデータと大きな同時実行性の場合、マルチテーブルの関連付けはデータベースのパフォーマンスに深刻な影響を与えるため、ワイドテーブルクエリを検討できます。

3.まとめ

DDDレイヤリングは、主に各レイヤ間の結合の問題を解決するため、各レイヤが相互に影響を与えることはありません。各レイヤーでは、ドメインレイヤーの設計がシステム全体の中心であり、ドメイン駆動設計のコアアイデアを最もよく反映しています。その優れた設計は、将来のアーキテクチャの持続可能性と保守性を確保することです。

元のリンク:https://my.oschina.net/barryhome/blog/4913300

この記事がお役に立てば幸いです。私の公式アカウントをフォローし、キーワード[インタビュー]に返信して、Javaのコアナレッジポイントとインタビューギフトパッケージをまとめてください。共有する技術的な乾物の記事や関連資料がもっとあります。みんなが一緒に学び、進歩できるようにしましょう!

おすすめ

転載: blog.csdn.net/weixin_48182198/article/details/112863266