境界付きコンテキストを使用して Java アプリケーションを構築する

Linda Hamilton
リリース: 2024-11-07 18:40:03
オリジナル
527 人が閲覧しました

Using bounded contexts to build a Java application

境界付きコンテキストとは何ですか?

境界付きコンテキストは、ドメイン駆動設計 (DDD) の中核となるパターンの 1 つです。これは、大規模なプロジェクトをドメインに分割する方法を表します。この分離により、柔軟性が高まり、メンテナンスが容易になります。

ヘキサゴナルアーキテクチャとは何ですか?

ヘキサゴナル アーキテクチャは、アプリケーションのコアを外部の依存関係から分離します。ポートとアダプターを使用して、ビジネス ロジックを外部サービスから分離します。ビジネス ロジックをフレームワーク、データベース、ユーザー インターフェイスから独立させることで、アプリケーションは将来の要件に簡単に適応できるようになります。

アーキテクチャは 3 つの主要コンポーネントで構成されています:

  1. ビジネスモデル: ビジネスルールとコアロジック。外部の依存関係から完全に分離されており、ポート経由でのみ通信します。
  2. ポート: ビジネス モデルへの出口と入口。コアを外​​部層から分離します。
  3. アダプター: 外部対話 (HTTP リクエスト、データベース操作) をコアが理解できるものに変換します。受信通信に使用される in アダプターと、送信通信に使用される out アダプターがあります。

なぜ六角形のアーキテクチャを使用するのでしょうか?

  • テスト容易性: データベース、外部 API、またはフレームワークをモックせずに、ビジネス ロジックの単体テストを作成できます。
  • 保守性: コアのビジネス ロジックに影響を与えることなく、依存関係を簡単に交換できます。
  • スケーラビリティ: レイヤーの独立したスケーリングにより、全体的なパフォーマンスが向上します。
  • 柔軟性: 異なる外部システムが同じコア ロジックと対話できます。

Java でヘキサゴナル アーキテクチャを使用してアプリケーションを構築する

このウォークスルー プロジェクトでは、Java の境界付きコンテキストとヘキサゴナル アーキテクチャを使用します。

目標は、Techtopia という遊園地のチケット発行システムを作成することです。このプロジェクトには、チケット、アトラクション、入場ゲートという 3 つの主要な境界コンテキストがあります。それぞれの境界付きコンテキストにはディレクトリがあり、ポート、アダプター、ユースケースなどのコンポーネントが含まれます。

公園のチケットを購入するコード プロセスを順に見ていきます。

  1. ドメインを定義します

「ドメイン」ディレクトリを作成し、フレームワークや外部依存関係のないビジネス ロジックを含めます。

「チケット」エンティティを作成します。

package java.boundedContextA.domain;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.UUID;

@Getter
@Setter
@ToString
public class Ticket {
    private TicketUUID ticketUUID;
    private LocalDateTime start;
    private LocalDateTime end;
    private double price;
    private TicketAction ticketAction;
    private final Guest.GuestUUID owner;
    private ActivityWindow activityWindow;

    public record TicketUUID(UUID uuid) {
    }

    public Ticket(TicketUUID ticketUUID, Guest.GuestUUID owner) {
        this.ticketUUID = ticketUUID;
        this.owner = owner;
    }

    public Ticket(TicketUUID ticketUUID, LocalDateTime start, LocalDateTime end, double price, TicketAction ticketAction, Guest.GuestUUID owner) {
        this.ticketUUID = ticketUUID;
        this.start = start;
        this.end = end;
        this.price = price;
        this.ticketAction = ticketAction;
        this.owner = owner;
    }

    public Ticket(TicketUUID ticketUUID, LocalDateTime start, LocalDateTime end, double price, Guest.GuestUUID owner, ActivityWindow activityWindow) {
        this.ticketUUID = ticketUUID;
        this.start = start;
        this.end = end;
        this.price = price;
        this.owner = owner;
        this.activityWindow = activityWindow;
    }

    public void addTicketActivity(TicketActivity ticketActivity) {
        this.activityWindow.add(ticketActivity);
    }
}
ログイン後にコピー
ログイン後にコピー

さらに、"BuyTicket" という名前の別のドメイン クラスを作成します。

package java.boundedContextA.domain;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.UUID;

@Getter
@Setter
@ToString
public class Ticket {
    private TicketUUID ticketUUID;
    private LocalDateTime start;
    private LocalDateTime end;
    private double price;
    private TicketAction ticketAction;
    private final Guest.GuestUUID owner;
    private ActivityWindow activityWindow;

    public record TicketUUID(UUID uuid) {
    }

    public Ticket(TicketUUID ticketUUID, Guest.GuestUUID owner) {
        this.ticketUUID = ticketUUID;
        this.owner = owner;
    }

    public Ticket(TicketUUID ticketUUID, LocalDateTime start, LocalDateTime end, double price, TicketAction ticketAction, Guest.GuestUUID owner) {
        this.ticketUUID = ticketUUID;
        this.start = start;
        this.end = end;
        this.price = price;
        this.ticketAction = ticketAction;
        this.owner = owner;
    }

    public Ticket(TicketUUID ticketUUID, LocalDateTime start, LocalDateTime end, double price, Guest.GuestUUID owner, ActivityWindow activityWindow) {
        this.ticketUUID = ticketUUID;
        this.start = start;
        this.end = end;
        this.price = price;
        this.owner = owner;
        this.activityWindow = activityWindow;
    }

    public void addTicketActivity(TicketActivity ticketActivity) {
        this.activityWindow.add(ticketActivity);
    }
}
ログイン後にコピー
ログイン後にコピー

*BuyTicket * はチケットを購入するためのロジックを表します。これを別の Spring コンポーネントにすることで、チケット購入ロジックをクラス内に分離でき、他のコンポーネントから独立して進化できます。この分離により保守性が向上し、コードベースがよりモジュール化されます。

  1. ポートの作成

「ports/in」ディレクトリでユースケースを作成します。ここではチケットを購入するユースケースを作成します。

package java.boundedContextA.domain;

import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.UUID;

@Component
public class BuyTicket {
    public Ticket buyTicket(TicketAction ticketAction, LocalDateTime start, LocalDateTime end, double price, Guest.GuestUUID owner) {
        return new Ticket(new Ticket.TicketUUID(UUID.randomUUID()), start, end, price, ticketAction, owner);
    }
}
ログイン後にコピー

チケットのレコードを作成して保存します。

package java.boundedContextA.ports.in;

public interface BuyingATicketUseCase {
    void buyTicket(BuyTicketsAmountCommand buyTicketsAmountCommand);
}
ログイン後にコピー

次に、"ports/out" ディレクトリに、チケット購入の各ステップを表すポートを作成します。 "CreateTicketPort""TicketLoadPort""TicketUpdatePort" のようなインターフェイスを作成します。

package java.boundedContextA.ports.in;

import java.boundedContextA.domain.Guest;
import java.boundedContextA.domain.TicketAction;

import java.time.LocalDateTime;

public record BuyTicketsAmountCommand(double price, TicketAction action, LocalDateTime start, LocalDateTime end, Guest.GuestUUID owner) {}
ログイン後にコピー
  1. ポートインターフェイスの作成

「core」という名前の別のディレクトリに、チケット購入のユースケースのインターフェイスを実装します。

package java.boundedContextA.ports.out;

import java.boundedContextA.domain.Ticket;

public interface TicketCreatePort {
    void createTicket(Ticket ticket);
}
ログイン後にコピー
  1. アダプターの作成

"adapters/out" ディレクトリで、ドメインをミラーリングするチケットの JPA エンティティを作成します。これは、アプリケーションがデータベースと通信し、チケットのテーブルを作成する方法です。

package java.boundedContextA.core;

import java.boundedContextA.domain.BuyTicket;
import java.boundedContextA.ports.in.BuyTicketsAmountCommand;
import java.boundedContextA.ports.in.BuyingATicketUseCase;
import java.boundedContextA.ports.out.TicketCreatePort;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@AllArgsConstructor
public class DefaultBuyingATicketUseCase implements BuyingATicketUseCase {
    final BuyTicket buyTicket;
    private final List<TicketCreatePort> ticketCreatePorts;

    @Override
    public void buyTicket(BuyTicketsAmountCommand buyTicketsAmountCommand) {
        var ticket = buyTicket.buyTicket(buyTicketsAmountCommand.action(), buyTicketsAmountCommand.start(), buyTicketsAmountCommand.end(), buyTicketsAmountCommand.price(), buyTicketsAmountCommand.owner());
        ticketCreatePorts.stream().forEach(ticketCreatedPort -> ticketCreatedPort.createTicket(ticket));
    }
}
ログイン後にコピー

エンティティのリポジトリを作成することを忘れないでください。このリポジトリは、他のアーキテクチャと同様に、サービスと通信します。

package java.adapters.out.db;

import java.boundedContextA.domain.TicketAction;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.JdbcTypeCode;

import java.sql.Types;
import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@Table(schema="boundedContextA",name = "boundedContextA.tickets")
@Getter
@Setter
@NoArgsConstructor
public class TicketBoughtJpaEntity {
    @Id
    @JdbcTypeCode(Types.VARCHAR)
    private UUID uuid;

    public TicketBoughtJpaEntity(UUID uuid) {
        this.uuid = uuid;
    }

    @JdbcTypeCode(Types.VARCHAR)
    private UUID owner;

    @Column
    private LocalDateTime start;
    @Column
    private LocalDateTime end;

    @Column
    private double price;
}
ログイン後にコピー

"adapters/in" ディレクトリに、チケットのコントローラーを作成します。このアプリケーションは外部ソースと通信します。

package java.boundedContextA.adapters.out.db;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.UUID;

public interface TicketRepository extends JpaRepository<TicketBoughtJpaEntity, UUID> {
    Optional<TicketBoughtJpaEntity> findByOwner(UUID uuid);
}
ログイン後にコピー
  1. チケット購入プロセスを完了します

チケットが購入されたことを示すには、"events" ディレクトリにイベントの記録を作成します。

イベントは、システムが他のシステムまたはコンポーネントと通信するために重要な、アプリケーション内での重要な出来事を表します。これらは、終了したプロセス、変化した状態、またはさらなるアクションの必要性について外部と通信する別の方法として機能します。

package java.boundedContextA.adapters.in;

import java.boundedContextA.ports.in.BuyTicketsAmountCommand;
import java.boundedContextA.ports.in.BuyingATicketUseCase;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class TicketsController {
    private final BuyingATicketUseCase buyingATicketUseCase;

    public TicketsController(BuyingATicketUseCase buyingATicketUseCase) {
        this.buyingATicketUseCase = buyingATicketUseCase;
    }

    @PostMapping("/ticket")
    public void receiveMoney(@RequestBody BuyTicketsAmountCommand command) {
        try {
            buyingATicketUseCase.buyTicket(command);
        } catch (IllegalArgumentException e) {
            System.out.println("An IllegalArgumentException occurred: " + e.getMessage());
        }
    }
}
ログイン後にコピー

すべてを一度に実行するためにメインクラスを含めることを忘れないでください。

package java.boundedContextA.events;

import java.time.LocalDateTime;
import java.util.UUID;

public record TicketIsBoughtEvent(UUID uuid, LocalDateTime start, LocalDateTime end) {
}
ログイン後にコピー

**これは非常に簡単な説明です。より詳細なコードと React インターフェイスへの接続方法については、この GitHub リポジトリを確認してください: https://github.com/alexiacismaru/techtopia.

結論

このアーキテクチャを Java で実装するには、ビジネス ロジックとインターフェイスを備えたクリーンなコア ドメインを定義し、外部システムと対話するアダプターを作成し、コアを分離したまますべてをまとめて記述する必要があります。

このアーキテクチャに従うことで、Java アプリケーションはより適切に構造化され、保守が容易になり、将来の変更に適応するのに十分な柔軟性が得られます。

以上が境界付きコンテキストを使用して Java アプリケーションを構築するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート