有界上下文是領域驅動設計(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中文網其他相關文章!