Organisering af lag ved hjælp af sekskantet arkitektur, DDD og forår

Java Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

I denne vejledning implementerer vi en Spring-applikation ved hjælp af DDD. Derudover organiserer vi lag ved hjælp af Hexagonal Architecture.

Med denne tilgang kan vi let udveksle de forskellige lag i applikationen.

2. Sekskantet arkitektur

Sekskantet arkitektur er en model for designe softwareapplikationer omkring domænelogik at isolere det fra eksterne faktorer.

Domænet logik er specificeret i en forretningskerne, som vi kalder den indvendige del, resten er uden for dele. Adgang til domænelogik udefra er tilgængelig via porte og adaptere.

3. Principper

For det første skal vi definere principper for at opdele vores kode. Som allerede forklaret kort definerer sekskantet arkitektur den indvendige og den udvendige del.

Hvad vi i stedet gør er at opdele vores applikation i tre lag; applikation (udenfor), domæne (inde) og infrastruktur (udenfor):

igennem applikationslaget, brugeren eller ethvert andet program interagerer med applikationen. Dette område skal indeholde ting som brugergrænseflader, RESTful-controllere og JSON-serialiseringsbiblioteker. Det inkluderer alt der udsætter adgang til vores ansøgning og orkestrerer udførelsen af ​​domænelogik.

I domænelaget opbevarer vi koden, der rører og implementerer forretningslogik. Dette er kernen i vores ansøgning. Derudover skal dette lag isoleres fra både applikationsdelen og infrastrukturdelen. Oven i det skal det også indeholde grænseflader, der definerer API'en til at kommunikere med eksterne dele, som databasen, som domænet interagerer med.

Endelig er det infrastrukturlag er den del, der indeholder alt, hvad applikationen har brug for for at arbejde såsom databasekonfiguration eller fjederkonfiguration. Desuden implementerer det også infrastrukturafhængige grænseflader fra domænet.

4. Domænelag

Lad os begynde med at implementere vores kernelag, som er domænelaget.

For det første skal vi oprette Bestille klasse:

offentlig klasse Bestil {privat UUID-id; privat OrderStatus-status; private listeordrevarer; privat BigDecimal pris; offentlig orden (UUID-id, produktprodukt) {this.id = id; this.orderItems = ny ArrayList (Arrays.astList (nyt OrderItem (produkt))); this.status = OrderStatus.CREATED; this.price = product.getPrice (); } offentlig tomrum komplet () {validateState (); this.status = OrderStatus.COMPLETED; } offentlig ugyldig addOrder (produktprodukt) {validateState (); validateProduct (produkt); orderItems.add (nyt OrderItem (produkt)); pris = pris.add (product.getPrice ()); } public void removeOrder (UUID id) {validateState (); endelig OrderItem orderItem = getOrderItem (id); orderItems.remove (orderItem); pris = price.subtract (orderItem.getPrice ()); } // getters}

Dette er vores samlede rod. Alt relateret til vores forretningslogik gennemgår denne klasse. Derudover Bestille er ansvarlig for at holde sig i den korrekte tilstand:

  • Ordren kan kun oprettes med det givne ID og baseret på et Produkt - konstruktøren selv indleder også ordren med OPRETTET status
  • Når ordren er gennemført, ændres den Bestillingsvares er umuligt
  • Det er umuligt at ændre Bestille uden for domæneobjektet, som med en setter

Desuden er den Bestille klasse er også ansvarlig for at skabe sin Bestillingsvare.

Lad os oprette Bestillingsvare klasse derefter:

offentlig klasse OrderItem {privat UUID productId; privat BigDecimal pris; public OrderItem (Product product) {this.productId = product.getId (); this.price = product.getPrice (); } // getters}

Som vi kan se, Bestillingsvare er oprettet på baggrund af en Produkt. Den gemmer henvisningen til den og gemmer den aktuelle pris på Produkt.

Derefter opretter vi en lagergrænseflade (a Havn i sekskantet arkitektur). Implementeringen af ​​grænsefladen vil være i infrastrukturlaget:

offentlig grænseflade OrderRepository {Valgfri findById (UUID id); ugyldig gemme (ordreordre); }

Endelig skal vi sørge for, at Bestille gemmes altid efter hver handling. At gøre det, vi definerer en domænetjeneste, som normalt indeholder logik, der ikke kan være en del af vores rod:

offentlig klasse DomainOrderService implementerer OrderService {privat endelig OrderRepository orderRepository; public DomainOrderService (OrderRepository orderRepository) {this.orderRepository = orderRepository; } @ Override public UUID createOrder (Product product) {Order order = new Order (UUID.randomUUID (), product); orderRepository.save (ordre); returner order.getId (); } @ Override public void addProduct (UUID id, Product product) {Order order = getOrder (id); order.addOrder (produkt); orderRepository.save (ordre); } @ Override offentlig ugyldig completeOrder (UUID id) {Order order = getOrder (id); order.complete (); orderRepository.save (ordre); } @ Overstyr offentlig ugyldig deleteProduct (UUID id, UUID productId) {Bestillingsordre = getOrder (id); order.removeOrder (productId); orderRepository.save (ordre); } privat ordre getOrder (UUID id) {return orderRepository .findById (id) .orElseThrow (RuntimeException :: new); }}

I en sekskantet arkitektur er denne service en adapter, der implementerer porten. Derudover vi registrerer det ikke som en springbønnefordi dette set fra et domæne er i den indvendige del, og fjederkonfigurationen er på ydersiden. Vi tilslutter det manuelt med Spring i infrastrukturlaget lidt senere.

Fordi domænelaget er helt afkoblet fra applikations- og infrastrukturlag vikan også test det uafhængigt:

klasse DomainOrderServiceUnitTest {privat OrderRepository orderRepository; privat DomainOrderService testet; @BeforeEach ugyldig setUp () {orderRepository = mock (OrderRepository.class); testet = ny DomainOrderService (orderRepository); } @Test ugyldig skalCreateOrder_thenSaveIt () {final product product = new Product (UUID.randomUUID (), BigDecimal.TEN, "productName"); endelig UUID id = testet.createOrder (produkt); verificere (orderRepository) .save (enhver (Order.class)); assertNotNull (id); }}

5. Applikationslag

I dette afsnit implementerer vi applikationslaget. Vi tillader brugeren at kommunikere med vores applikation via en RESTful API.

Lad os derfor oprette OrderController:

@RestController @RequestMapping ("/ orders") offentlig klasse OrderController {privat OrderService orderService; @Autowired offentlig OrderController (OrderService orderService) {this.orderService = orderService; } @PostMapping CreateOrderResponse createOrder (@RequestBody CreateOrderRequest anmodning) {UUID id = orderService.createOrder (request.getProduct ()); returner nyt CreateOrderResponse (id); } @PostMapping (værdi = "/ {id} / produkter") ugyldig addProduct (@PathVariable UUID id, @RequestBody AddProductRequest anmodning) {orderService.addProduct (id, request.getProduct ()); } @DeleteMapping (værdi = "/ {id} / produkter") ugyldig deleteProduct (@PathVariable UUID id, @RequestParam UUID productId) {orderService.deleteProduct (id, productId); } @PostMapping ("/ {id} / complete") ugyldig completeOrder (@PathVariable UUID id) {orderService.completeOrder (id); }}

Denne enkle Spring Rest-controller er ansvarlig for at organisere udførelsen af ​​domænelogik.

Denne controller tilpasser den udvendige RESTful interface til vores domæne. Det gør det ved at kalde de passende metoder fra OrderService (Havn).

6. Infrastrukturlag

Infrastrukturlaget indeholder den nødvendige logik til at køre applikationen.

Derfor starter vi med at oprette konfigurationsklasser. Lad os først implementere en klasse, der registrerer vores OrderService som en springbønne:

@Configuration public class BeanConfiguration {@Bean OrderService orderService (OrderRepository orderRepository) {returner nyt DomainOrderService (orderRepository); }}

Lad os derefter oprette den konfiguration, der er ansvarlig for at aktivere Spring Data-arkiver, vi bruger:

@EnableMongoRepositories (basePackageClasses = SpringDataMongoOrderRepository.class) offentlig klasse MongoDBConfiguration {}

Vi har brugt basePackageClasses ejendom, fordi disse arkiver kun kan være i infrastrukturlaget. Derfor er der ingen grund til, at Spring scanner hele applikationen. Desuden kan denne klasse indeholde alt relateret til oprettelse af en forbindelse mellem MongoDB og vores applikation.

Endelig implementerer vi OrderRepository fra domænelaget. Vi bruger vores SpringDataMongoOrderRepository i vores implementering:

@Komponent offentlig klasse MongoDbOrderRepository implementerer OrderRepository {private SpringDataMongoOrderRepository orderRepository; @Autowired offentlig MongoDbOrderRepository (SpringDataMongoOrderRepository orderRepository) {this.orderRepository = orderRepository; } @ Override public Valgfri findById (UUID id) {return orderRepository.findById (id); } @ Overstyr offentlig ugyldig gem (ordreordre) {orderRepository.save (ordre); }}

Denne implementering gemmer vores Bestille i MongoDB. I en sekskantet arkitektur er denne implementering også en adapter.

7. Fordele

Den første fordel ved denne tilgang er, at vi separat arbejde for hvert lag. Vi kan fokusere på et lag uden at påvirke andre.

Desuden er de naturligvis lettere at forstå, fordi hver af dem fokuserer på dets logik.

En anden stor fordel er, at vi har isoleret domænelogikken fra alt andet. Domænedelen indeholder kun forretningslogik og kan let flyttes til et andet miljø.

Lad os faktisk ændre infrastrukturlaget for at bruge Cassandra som en database:

@Komponent offentlig klasse CassandraDbOrderRepository implementerer OrderRepository {private final SpringDataCassandraOrderRepository orderRepository; @Autowired offentlig CassandraDbOrderRepository (SpringDataCassandraOrderRepository orderRepository) {this.orderRepository = orderRepository; } @ Override public Valgfri findById (UUID id) {Valgfri orderEntity = orderRepository.findById (id); hvis (orderEntity.isPresent ()) {return Optional.of (orderEntity.get () .toOrder ()); } ellers {returner Optional.empty (); }} @ Overstyr offentlig tomrum gemme (ordreordre) {orderRepository.save (ny OrderEntity (ordre)); }}

I modsætning til MongoDB bruger vi nu en OrderEntity for at fastholde domænet i databasen.

Hvis vi tilføjer teknologispecifikke kommentarer til vores Bestille domæneobjekt, derefter vi overtræder afkoblingen mellem infrastruktur og domænelag.

Depotet tilpasser domænet til vores vedvarende behov.

Lad os gå et skridt videre og omdanne vores RESTful-applikation til en kommandolinjeapplikation:

@Komponent offentlig klasse CliOrderController {privat statisk endelig Logger LOG = LoggerFactory.getLogger (CliOrderController.class); privat endelig OrderService orderService; @Autowired offentlig CliOrderController (OrderService orderService) {this.orderService = orderService; } offentlig tomrum createCompleteOrder () {LOG.info ("<>"); UUID orderId = createOrder (); orderService.completeOrder (orderId); } offentlig tomrum createIncompleteOrder () {LOG.info ("<>"); UUID orderId = createOrder (); } privat UUID createOrder () {LOG.info ("Afgivelse af en ny ordre med to produkter"); Produkt mobilePhone = nyt produkt (UUID.randomUUID (), BigDecimal.valueOf (200), "mobil"); Produkt barbermaskine = nyt produkt (UUID.randomUUID (), BigDecimal.valueOf (50), "barbermaskine"); LOG.info ("Oprettelse af ordre med mobiltelefon"); UUID orderId = orderService.createOrder (mobilePhone); LOG.info ("Tilføjelse af en barbermaskine til ordren"); orderService.addProduct (orderId, barbermaskine); returnere OrderId; }}

I modsætning til tidligere har vi nu fastgjort et sæt foruddefinerede handlinger, der interagerer med vores domæne. Vi kunne f.eks. Bruge dette til at udfylde vores applikation med spottede data.

Selvom vi fuldstændigt ændrede formålet med applikationen, har vi ikke rørt domænet laget.

8. Konklusion

I denne artikel har vi lært, hvordan man adskiller logikken relateret til vores applikation i specifikke lag.

For det første definerede vi tre hovedlag: applikation, domæne og infrastruktur. Derefter beskrev vi, hvordan man udfyldte dem og forklarede fordelene.

Derefter kom vi med implementeringen for hvert lag:

Endelig byttede vi applikations- og infrastrukturlagene uden at påvirke domænet.

Som altid er koden til disse eksempler tilgængelig på GitHub.

Java bund

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

$config[zx-auto] not found$config[zx-overlay] not found