境界付きコンテキストは、ドメイン駆動設計 (DDD) の中核となるパターンの 1 つです。これは、大規模なプロジェクトをドメインに分割する方法を表します。この分離により、柔軟性が高まり、メンテナンスが容易になります。
ヘキサゴナル アーキテクチャは、アプリケーションのコアを外部の依存関係から分離します。ポートとアダプターを使用して、ビジネス ロジックを外部サービスから分離します。ビジネス ロジックをフレームワーク、データベース、ユーザー インターフェイスから独立させることで、アプリケーションは将来の要件に簡単に適応できるようになります。
アーキテクチャは 3 つの主要コンポーネントで構成されています:
このウォークスルー プロジェクトでは、Java の境界付きコンテキストとヘキサゴナル アーキテクチャを使用します。
目標は、Techtopia という遊園地のチケット発行システムを作成することです。このプロジェクトには、チケット、アトラクション、入場ゲートという 3 つの主要な境界コンテキストがあります。それぞれの境界付きコンテキストにはディレクトリがあり、ポート、アダプター、ユースケースなどのコンポーネントが含まれます。
公園のチケットを購入するコード プロセスを順に見ていきます。
「ドメイン」ディレクトリを作成し、フレームワークや外部依存関係のないビジネス ロジックを含めます。
「チケット」エンティティを作成します。
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 コンポーネントにすることで、チケット購入ロジックをクラス内に分離でき、他のコンポーネントから独立して進化できます。この分離により保守性が向上し、コードベースがよりモジュール化されます。
「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) {}
「core」という名前の別のディレクトリに、チケット購入のユースケースのインターフェイスを実装します。
package java.boundedContextA.ports.out; import java.boundedContextA.domain.Ticket; public interface TicketCreatePort { void createTicket(Ticket ticket); }
"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); }
チケットが購入されたことを示すには、"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 サイトの他の関連記事を参照してください。