有界上下文是领域驱动设计(DDD)的核心模式之一。它代表了如何将一个大项目划分为多个域。这种分离提供了灵活性并且更容易维护。
六边形架构将应用程序的核心与其外部依赖项分开。它使用端口和适配器将业务逻辑与外部服务分离。使业务逻辑独立于框架、数据库或用户界面,使应用程序能够轻松适应未来的需求。
该架构由三个主要组件组成:
这个演练项目在 Java 中使用有界上下文和六边形架构。
我们的目标是为一个名为 Techtopia 的游乐园创建一个票务系统。该项目有 3 个主要的有界上下文:门票、景点和入口。每个有界上下文都有其目录,并包括诸如 in 和我们的端口、适配器、用例等组件。
我们将逐步完成购买公园门票的代码流程。
创建一个“domain”目录并包含业务逻辑,不受任何框架或外部依赖。
创建“Ticket”实体。
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”目录中,创建Ticket的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”目录中,创建Ticket的控制器。此应用程序将与外部源通信。
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中文网其他相关文章!