>  기사  >  Java  >  제한된 컨텍스트를 사용하여 Java 애플리케이션 구축

제한된 컨텍스트를 사용하여 Java 애플리케이션 구축

Linda Hamilton
Linda Hamilton원래의
2024-11-07 18:40:03456검색

Using bounded contexts to build a Java application

제한된 컨텍스트란 무엇입니까?

제한된 컨텍스트는 도메인 기반 디자인(DDD)의 핵심 패턴 중 하나입니다. 대규모 프로젝트를 도메인으로 나누는 방법을 나타냅니다. 이러한 분리를 통해 유연성이 향상되고 유지 관리가 더 쉬워집니다.

육각형 아키텍처란 무엇입니까?

육각형 아키텍처는 애플리케이션의 핵심을 외부 종속성과 분리합니다. 포트와 어댑터를 사용하여 비즈니스 로직을 외부 서비스와 분리합니다. 비즈니스 로직을 프레임워크, 데이터베이스 또는 사용자 인터페이스와 독립적으로 만들면 애플리케이션이 향후 요구 사항에 쉽게 적응할 수 있습니다.

아키텍처는 세 가지 주요 구성 요소로 구성됩니다.

  1. 비즈니스 모델: 비즈니스 규칙 및 핵심 논리. 외부 종속성과 완전히 격리되어 있으며 포트를 통해서만 통신합니다.
  2. 포트: 비즈니스 모델의 출구와 진입. 코어를 외부 레이어와 분리합니다.
  3. 어댑터: 외부 상호 작용(HTTP 요청, 데이터베이스 작업)을 핵심이 이해할 수 있는 것으로 변환합니다. 들어오는 통신에 사용되는 in 어댑터와 나가는 통신에 사용되는 out 어댑터가 있습니다.

육각형 아키텍처를 사용하는 이유는 무엇입니까?

  • 테스트 가능성: 데이터베이스, 외부 API 또는 프레임워크를 모의하지 않고도 비즈니스 로직에 대한 단위 테스트를 작성할 수 있습니다.
  • 유지관리성: 핵심 비즈니스 로직에 영향을 주지 않고 종속성을 쉽게 교체할 수 있습니다.
  • 확장성: 레이어를 독립적으로 확장하여 전반적인 성능을 향상합니다.
  • 유연성: 다양한 외부 시스템이 동일한 핵심 로직과 상호 작용할 수 있습니다.

Java에서 Hexagonal Architecture를 사용하여 애플리케이션 구축

이 연습 프로젝트는 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.