Maison  >  Article  >  Java  >  Utiliser des contextes limités pour créer une application Java

Utiliser des contextes limités pour créer une application Java

Linda Hamilton
Linda Hamiltonoriginal
2024-11-07 18:40:03456parcourir

Using bounded contexts to build a Java application

Que sont les contextes délimités ?

Un contexte délimité est l'un des modèles de base de la conception pilotée par domaine (DDD). Il représente comment diviser un grand projet en domaines. Cette séparation permet une flexibilité et un entretien plus facile.

Qu'est-ce qu'une architecture hexagonale ?

L'architecture hexagonale sépare le cœur de l'application de ses dépendances externes. Il utilise des ports et des adaptateurs pour découpler la logique métier des services externes. Rendre la logique métier indépendante des frameworks, des bases de données ou des interfaces utilisateur permet à l'application de s'adapter facilement aux exigences futures.

L'architecture est composée de trois composants principaux :

  1. Business Model : les règles commerciales et la logique de base. Il est complètement isolé des dépendances externes et ne communique que via des ports.
  2. Ports : sortie et entrée au business model. Ils séparent le noyau des couches externes.
  3. Adaptateurs : traduisent les interactions externes (requêtes HTTP, opérations de base de données) en quelque chose que le cœur comprend. Il existe des adaptateurs entrants utilisés pour les communications entrantes et des adaptateurs sortants utilisés pour les communications sortantes.

Pourquoi utiliser l'architecture hexagonale ?

  • Testabilité : vous pouvez écrire des tests unitaires pour la logique métier sans vous moquer des bases de données, des API externes ou des frameworks.
  • Maintenabilité : vous pouvez facilement échanger les dépendances sans affecter la logique métier de base.
  • Évolutivité : mise à l'échelle indépendante des couches, améliorant les performances globales.
  • Flexibilité : différents systèmes externes peuvent interagir avec la même logique de base.

Création d'une application utilisant l'architecture hexagonale en Java

Ce projet pas à pas utilise des contextes délimités et une architecture hexagonale en Java.

Le but est de créer un système de billetterie pour un parc d'attractions appelé Techtopia. Le projet comporte 3 contextes principaux délimités : billets, attractions et portes d'entrée. Chaque contexte délimité a son répertoire et comprend des composants comme in et nos ports, adaptateurs, cas d'utilisation, etc.

Nous allons parcourir le processus de code d'achat d'un billet pour le parc.

  1. Définir le domaine

Créez un répertoire « domaine » et incluez la logique métier, libre de tout framework ou dépendance externe.

Créez l'entité "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);
    }
}

De plus, créez une autre classe de domaine nommée "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 * représente la logique d'achat d'un billet. En en faisant un composant Spring distinct, vous pouvez isoler la logique d'achat de billets dans sa classe, qui peut évoluer indépendamment des autres composants. Cette séparation améliore la maintenabilité et rend la base de code plus modulaire.

  1. Créer des ports

Dans le répertoire "ports/in" vous créez des cas d'utilisation. Ici, nous ferons le cas d'utilisation où un ticket est acheté.

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);
    }
}

Créez un enregistrement d'un ticket pour le sauvegarder.

package java.boundedContextA.ports.in;

public interface BuyingATicketUseCase {
    void buyTicket(BuyTicketsAmountCommand buyTicketsAmountCommand);
}

Ensuite, dans le répertoire "ports/out" vous créez des ports qui représentent chaque étape de l'achat dudit ticket. Créez des interfaces telles que "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. Créer des interfaces de ports

Dans un répertoire séparé, nommé "core", implémentez l'interface du cas d'utilisation du ticket d'achat.

package java.boundedContextA.ports.out;

import java.boundedContextA.domain.Ticket;

public interface TicketCreatePort {
    void createTicket(Ticket ticket);
}
  1. Créer des adaptateurs

Dans le répertoire "adapters/out", créez les entités JPA du Ticket pour mettre en miroir le domaine. C'est ainsi que l'application communique avec la base de données et crée un tableau des tickets.

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));
    }
}

N'oubliez pas de créer un référentiel de l'entité. Ce référentiel communiquera avec le service, comme toute autre architecture.

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;
}

Dans le répertoire "adapters/in", créez un contrôleur du Ticket. Cette application communiquera avec des sources externes.

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. Finaliser le processus d'achat des billets

Pour signifier que le billet a été acheté, créez un enregistrement de l'événement dans un répertoire "événements".

Les événements représentent des occurrences significatives dans l'application qui sont importantes pour que le système communique avec d'autres systèmes ou composants. Ils constituent un autre moyen de communiquer avec l'extérieur sur un processus terminé, un état qui a changé ou la nécessité d'une action supplémentaire.

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());
        }
    }
}

N'oubliez pas d'inclure une classe principale pour tout exécuter en même temps.

package java.boundedContextA.events;

import java.time.LocalDateTime;
import java.util.UUID;

public record TicketIsBoughtEvent(UUID uuid, LocalDateTime start, LocalDateTime end) {
}

**Ceci est une explication très brève, pour un code plus approfondi et comment se connecter à une interface React, consultez ce référentiel GitHub : https://github.com/alexiacismaru/techtopia.

Conclusion

La mise en œuvre de cette architecture en Java implique de définir un domaine central propre avec une logique métier et des interfaces, de créer des adaptateurs pour interagir avec des systèmes externes et de tout écrire ensemble tout en gardant le noyau isolé.

En suivant cette architecture, vos applications Java seront mieux structurées, plus faciles à maintenir et suffisamment flexibles pour s'adapter aux changements futurs.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn