首頁  >  文章  >  Java  >  使用有界上下文建立 Java 應用程式

使用有界上下文建立 Java 應用程式

Linda Hamilton
Linda Hamilton原創
2024-11-07 18:40:03536瀏覽

Using bounded contexts to build a Java application

什麼是有界上下文?

有界上下文是領域驅動設計(DDD)的核心模式之一。它代表瞭如何將一個大項目劃分為多個域。這種分離提供了靈活性並且更容易維護。

什麼是六角形架構?

六邊形架構將應用程式的核心與其外部相依性分開。它使用連接埠和適配器將業務邏輯與外部服務分開。使業務邏輯獨立於框架、資料庫或使用者介面,使應用程式能夠輕鬆適應未來的需求。

此架構由三個主要元件組成:

  1. 商業模型:業務規則和核心邏輯。與外部依賴完全隔離,僅透過連接埠進行通訊。
  2. 連接埠:商業模式的退出和進入。它們將核心與外層分開。
  3. 適配器:將外部互動(HTTP 請求、資料庫操作)轉換為核心可以理解的內容。有用於傳入通訊的輸入適配器和用於傳出通訊的輸出適配器。

為什麼要使用六角形架構?

  • 可測試性:您可以為業務邏輯編寫單元測試,而無需模擬資料庫、外部 API 或框架。
  • 可維護性:您可以輕鬆地交換依賴項,而不影響核心業務邏輯。
  • 可擴充性:各層獨立縮放,提升整體效能。
  • 靈活性:不同的外部系統可以與相同的核心邏輯互動。

使用 Java 中的六角形架構建立應用程式

這個演練專案在 Java 中使用有界上下文和六邊形架構。

我們的目標是為一個名為 Techtopia 的遊樂園創建一個票務系統。該項目有 3 個主要的有界上下文:門票、景點和入口。每個有界上下文都有其目錄,並包括諸如 in 和我們的連接埠、適配器、用例等元件。

我們將逐步完成購買園區門票的代碼流程。

  1. 定義域

建立一個「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 元件,您可以將購票邏輯隔離在其類別中,該邏輯可以獨立於其他元件而發展。這種分離提高了可維護性並使程式碼庫更加模組化。

  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」目錄中,建立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);
}
  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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn