DDD Korlátozott összefüggések és Java modulok

1. Áttekintés

A tartományvezérelt tervezés (DDD) olyan elvek és eszközök összessége, amelyek segítenek hatékony szoftverarchitektúrák megtervezésében a magasabb üzleti érték elérése érdekében. A Bounded Context az egyik központi és alapvető minta az architektúra megmentésére a Big Ball Of Sudból, azáltal, hogy az egész alkalmazási területet több szemantikailag konzisztens részre osztja szét.

Ugyanakkor a Java 9 modulrendszerrel erősen tokozott modulokat hozhatunk létre.

Ebben az oktatóanyagban létrehozunk egy egyszerű áruház alkalmazást, és megismerhetjük, hogyan lehet kihasználni a Java 9 modulokat, miközben meghatározzuk a határokat a behatárolt kontextusok számára.

2. DDD korlátozott összefüggések

Manapság a szoftverrendszerek nem egyszerű CRUD alkalmazások. Valójában a tipikus monolit vállalati rendszer néhány régi kódbázisból és újonnan hozzáadott szolgáltatásokból áll. Az ilyen rendszereket azonban minden változtatással egyre nehezebb fenntartani. Végül teljesen fenntarthatatlanná válhat.

2.1. Korlátozott kontextus és mindenütt jelenlévő nyelv

A megoldott kérdés megoldásához a DDD biztosítja a Bounded Context fogalmát. A Bounded Context egy olyan tartomány logikai határa, ahol az egyes feltételek és szabályok következetesen érvényesek. Ezen a határon belül minden kifejezés, meghatározás és fogalom alkotja a mindenütt jelenlévő nyelvet.

Különösen a mindenütt jelenlévő nyelv legfőbb előnye, hogy a projekt különböző tagjai különböző üzleti területek köré csoportosulnak.

Ezenkívül több kontextus működhet ugyanazzal a dologgal. Mindazonáltal ezeknek az összefüggéseknek különböző jelentése lehet.

2.2. Rendelési kontextus

Kezdjük az alkalmazásunk megvalósításával a Rendelés kontextusának meghatározásával. Ez a kontextus két entitást tartalmaz: OrderItem és Vásárlói megrendelés.

A Vásárlói megrendelés az entitás egy összesített gyökér:

public class CustomerOrder {private int orderId; privát karakterlánc-fizetési módszer; privát String cím; private List orderItems; public float calcTotalPrice () {return orderItems.stream (). map (OrderItem :: getTotalPrice) .reduce (0F, Float :: sum); }}

Mint láthatjuk, ez az osztály tartalmazza a calcTotalPrice üzleti módszer. De egy valós projektben valószínűleg sokkal bonyolultabb lesz - például a végső árba beleszámítva az árengedményeket és az adókat.

Ezután hozzuk létre a OrderItem osztály:

public class OrderItem {private int productId; privát int mennyiség; magán úszó egységÁr; saját úszó egységSúly; }

Meghatároztuk az entitásokat, de ki kell tennünk néhány API-t az alkalmazás más részeinek is. Hozzuk létre a CustomerOrderService osztály:

public class A CustomerOrderService végrehajtja a OrderService {public static final String EVENT_ORDER_READY_FOR_SHIPMENT = "OrderReadyForShipmentEvent"; privát CustomerOrderRepository orderRepository; privát EventBus eventBus; @Orride public void placeOrder (CustomerOrder order) {this.orderRepository.saveCustomerOrder (order); Térkép hasznos teher = new HashMap (); payload.put ("order_id", String.valueOf (order.getOrderId ())); ApplicationEvent event = new ApplicationEvent (hasznos teher) {@Orride public String getType () {return EVENT_ORDER_READY_FOR_SHIPMENT; }}; this.eventBus.publish (esemény); }}

Itt néhány fontos szempontot kell kiemelnünk. A placeRendelés módszer felelős az ügyfél megrendelések feldolgozásáért. A megrendelés feldolgozása után az esemény közzétételre kerül a EventBus. Az eseményvezérelt kommunikációt a következő fejezetekben tárgyaljuk. Ez a szolgáltatás biztosítja az alapértelmezett megvalósítást a OrderService felület:

nyilvános felület A OrderService kiterjeszti az ApplicationService {void placeOrder (CustomerOrder megrendelés); void setOrderRepository (CustomerOrderRepository orderRepository); }

Továbbá ehhez a szolgáltatáshoz szükséges a CustomerOrderRepository megtartani a megrendeléseket:

nyilvános felület CustomerOrderRepository {void saveCustomerOrder (CustomerOrder order); }

Ami lényeges, az az ezt az interfészt nem ebben a kontextusban valósítják meg, hanem az Infrastruktúra modul biztosítja, mint később meglátjuk.

2.3. Szállítási kontextus

Most definiáljuk a szállítási kontextust. Ez is egyszerű és három entitást fog tartalmazni: Csomag, PackageItem, és ShippableOrder.

Kezdjük a ShippableOrder entitás:

nyilvános osztály ShippableOrder {private int orderId; privát String cím; privát Lista packageItems; }

Ebben az esetben az entitás nem tartalmazza a fizetési mód terület. Ennek oka, hogy a Szállítási környezetünkben nem érdekel, hogy melyik fizetési módot használják. A szállítási kontextus felelős a megrendelések szállításának feldolgozásáért.

Továbbá a Csomag az entitás a szállítási kontextusra jellemző:

nyilvános osztály Parcel {private int orderId; privát String cím; privát String trackingId; privát Lista packageItems; public float calcTotalWeight () {return packageItems.stream (). map (PackageItem :: getWeight) .reduce (0F, Float :: sum); } public boolean isTaxable () {return calcEstimatedValue ()> 100; } public float calcEstimatedValue () {return packageItems.stream (). map (PackageItem :: getWeight) .reduce (0F, Float :: sum); }}

Mint láthatjuk, sajátos üzleti módszereket is tartalmaz, és összesített gyökérként működik.

Végül definiáljuk a ParcelShippingService:

public class ParcelShippingService hajtja végre a ShippingService {public static final String EVENT_ORDER_READY_FOR_SHIPMENT = "OrderReadyForShipmentEvent"; privát ShippingOrderRepository orderRepository; privát EventBus eventBus; privát Térkép shippedParcels = új HashMap (); @Orride public void shipOrder (int orderId) {Opcionális order = this.orderRepository.findShippableOrder (orderId); order.ifPresent (completeOrder -> {Csomagcsomag = új csomag Küldje el a csomagot a this.shippedParcels.put (completeOrder.getOrderId (), parcel);}); } @Orride public void listenToOrderEvents () {this.eventBus.subscribe (EVENT_ORDER_READY_FOR_SHIPMENT, new EventSubscriber () {@Override public void onEvent (E event) {shipOrder (Integer.parseInt) "event.getPayloadV; }); } @Override public Opcionális getParcelByOrderId (int orderId) {return Optional.ofNullable (this.shippedParcels.get (orderId)); }}

Ez a szolgáltatás hasonlóan használja a ShippingOrderRepository a megrendelések lehívására id szerint. Ennél is fontosabb, hogy feliratkozik a OrderReadyForShipmentEvent esemény, amelyet egy másik kontextus tesz közzé. Amikor ez az esemény bekövetkezik, a szolgáltatás néhány szabályt alkalmaz, és feladja a megrendelést. Az egyszerűség kedvéért a kiszállított megrendeléseket a HashMap.

3. Helyi térképek

Eddig két összefüggést definiáltunk. Azonban nem állítottunk fel kifejezett kapcsolatokat közöttük. Erre a célra a DDD rendelkezik a Kontextus leképezés koncepcióval. A kontextustérkép a rendszer különböző kontextusai közötti kapcsolatok vizuális leírása. Ez a térkép megmutatja, hogy a különböző részek egymás mellett léteznek-e a tartomány létrehozásakor.

Öt fő kapcsolattípus létezik a korlátozott összefüggések között:

  • Partnerség - kapcsolat két összefüggés között, amelyek együttműködnek a két csapat és a függő célok összehangolásában
  • Megosztott kernel - egyfajta kapcsolat, amikor több kontextus közös részét kivonják egy másik kontextusba / modulba a kódduplikáció csökkentése érdekében
  • Ügyfél-beszállító - kapcsolat két olyan kontextus között, ahol az egyik kontextus (upstream) adatokat hoz létre, a másik (downstream) pedig felhasználja azokat. Ebben a kapcsolatban mindkét fél a lehető legjobb kommunikáció kialakításában érdekelt
  • Alkalmazkodó - ennek a kapcsolatnak van upstream és downstream is, azonban a downstream mindig megfelel az upstream API-jának
  • Korrupcióellenes réteg - ezt a típusú kapcsolatot széles körben használják a régi rendszerek számára, hogy adaptálják őket egy új architektúrához, és fokozatosan áttérjenek a régi kódbázisról. A korrupcióellenes réteg adapterként működik, hogy lefordítsa az upstream adatokat és megvédje a nem kívánt változásoktól

Sajátos példánkban a Megosztott kernel kapcsolatot fogjuk használni. Nem definiáljuk tiszta formájában, de leginkább a rendszer eseményeinek közvetítőjeként fog működni.

Így a SharedKernel modul nem tartalmaz konkrét megvalósításokat, csak interfészeket.

Kezdjük a EventBus felület:

nyilvános felület EventBus {void publish (E esemény); érvénytelen feliratkozás (String eventType, EventSubscriber előfizető); érvénytelen leiratkozás (String eventType, EventSubscriber előfizető); }

Ez a felület később kerül bevezetésre az Infrastruktúra modulunkban.

Ezután létrehozunk egy alapszolgáltatási felületet alapértelmezett módszerekkel az eseményvezérelt kommunikáció támogatásához:

nyilvános felület ApplicationService {alapértelmezett void publishEvent (E esemény) {EventBus eventBus = getEventBus (); if (eventBus! = null) {eventBus.publish (esemény); }} alapértelmezett érvénytelen feliratkozás (String eventType, EventSubscriber előfizető) {EventBus eventBus = getEventBus (); if (eventBus! = null) {eventBus.subscribe (eventType, előfizető); }} alapértelmezett érvénytelen leiratkozás (String eventType, EventSubscriber előfizető) {EventBus eventBus = getEventBus (); if (eventBus! = null) {eventBus.unsubscribe (eventType, előfizető); }} EventBus getEventBus (); void setEventBus (EventBus eventBus); }

Tehát a korlátozott összefüggésekben lévő szolgáltatási interfészek kibővítik ezt az interfészt, hogy közös legyen az eseményekhez kapcsolódó funkcionalitás.

4. Java 9 Modularitás

Itt az ideje annak feltárására, hogy a Java 9 modulrendszer hogyan tudja támogatni a meghatározott alkalmazásstruktúrát.

A Java Platform Module System (JPMS) megbízhatóbb és erősebben beágyazott modulok felépítését ösztönzi. Ennek eredményeként ezek a tulajdonságok elősegíthetik környezetünk elkülönítését és egyértelmű határok megállapítását.

Lássuk a végső modul diagramunkat:

4.1. SharedKernel modul

Kezdjük a SharedKernel modullal, amely nem függ más moduloktól. Így a module-info.java úgy néz ki, mint a:

modul com.baeldung.dddmodules.sharedkernel {export com.baeldung.dddmodules.sharedkernel.events; exportálja a com.baeldung.dddmodules.sharedkernel.service; }

Exportáljuk a modul interfészeket, így azok elérhetőek más modulok számára.

4.2. OrderContext Modul

Ezután helyezzük át a hangsúlyt a OrderContext modulra. Csak a SharedKernel modulban definiált interfészeket igényel:

com.baeldung.dddmodules.ordercontext modul {com_baeldung.dddmodules.sharedkernel szükséges; exportálja a com.baeldung.dddmodules.ordercontext.service; exportálja a com.baeldung.dddmodules.ordercontext.model; exportálja a com.baeldung.dddmodules.ordercontext.repository; com.baeldung.dddmodules.ordercontext.service.OrderService szolgáltatást nyújt a com.baeldung.dddmodules.ordercontext.service.CustomerOrderService szolgáltatással; }

Azt is láthatjuk, hogy ez a modul az alapértelmezett megvalósítást exportálja OrderService felület.

4.3. ShippingContext Modul

Az előző modulhoz hasonlóan hozzuk létre a ShippingContext moduldefiníciós fájlt:

modul com.baeldung.dddmodules.shippingcontext {com.baeldung.dddmodules.sharedkernel szükséges; exportálja a com.baeldung.dddmodules.shippingcontext.service; exportálja a com.baeldung.dddmodules.shippingcontext.model; exportálja a com.baeldung.dddmodules.shippingcontext.repository; com.baeldung.dddmodules.shippingcontext.service.ShippingService szolgáltatást nyújt a com.baeldung.dddmodules.shippingcontext.service.ParcelShippingService szolgáltatással; }

Ugyanígy exportáljuk a. Alapértelmezett megvalósítását ShippingService felület.

4.4. Infrastruktúra modul

Itt az ideje leírni az Infrastructure modult. Ez a modul a meghatározott interfészek megvalósításának részleteit tartalmazza. Kezdjük egy egyszerű megvalósítás létrehozásával a EventBus felület:

public class A SimpleEventBus megvalósítja az EventBus {private final Map-t előfizetők = new ConcurrentHashMap (); @Orride public void publish (E event) {if (subscriber.containsKey (event.getType ())) {subscriptioners.get (event.getType ()) .forEach (előfizető -> subscriber.onEvent (esemény)); }} @Orride public void subscribe (String eventType, EventSubscriber előfizető) {Set eventSubscribers = subscribers.get (eventType); if (eventSubscribers == null) {eventSubscribers = new CopyOnWriteArraySet (); előfizetők.put (eventType, eventSubscribers); } eventSubscribers.add (előfizető); } @Orride public void unsubscribe (String eventType, EventSubscriber előfizető) {if (subscriber.containsKey (eventType)) {subscriptioners.get (eventType) .remove (előfizető); }}}

Ezután végre kell hajtanunk a CustomerOrderRepository és ShippingOrderRepository interfészek. A legtöbb esetben a Rendelés Az entitás ugyanabban a táblázatban lesz tárolva, de más entitásmodellként használatos korlátozott összefüggésekben.

Nagyon gyakori, hogy egyetlen entitás vegyes kódot tartalmaz az üzleti tartomány különböző területeiről vagy alacsony szintű adatbázis-hozzárendelésekből. Megvalósításunk céljából felosztottuk entitásainkat a korlátozott összefüggések szerint: Vásárlói megrendelés és ShippableOrder.

Először hozzunk létre egy osztályt, amely egy teljes kitartó modellt képvisel:

public static class PersistenceOrder {public int orderId; public String paymentMethod; nyilvános karakterlánc-cím; public List orderItems; public static class OrderItem {public int productId; nyilvános úszó egységÁr; public float itemSúly; public int mennyiség; }}

Láthatjuk, hogy ez az osztály mindkét mező összes mezőjét tartalmazza Vásárlói megrendelés és ShippableOrder entitások.

A dolgok egyszerűsége érdekében szimuláljunk egy memóriában lévő adatbázist:

public class InMemoryOrderStore megvalósítja a CustomerOrderRepository, a ShippingOrderRepository {privát térkép megrendelésekDb = új HashMap (); @Orride public void saveCustomerOrder (CustomerOrder order) {this.ordersDb.put (order.getOrderId (), new PersistenceOrder (order.getOrderId (), order.getPaymentMethod (), order.getAddress (), order .getOrderItems () .stream () .map (orderItem -> new PersistenceOrder.OrderItem (orderItem.getProductId (), orderItem.getQuantity (), orderItem.getUnitWeight (), orderItem.getUnitPrice ())) .collect (Collectors.toList ())); } @Override public Opcionális findShippableOrder (int orderId) {if (! This.ordersDb.containsKey (orderId)) return Opcionális.empty (); PersistenceOrder orderRecord = this.ordersDb.get (orderId); return Az Optional.of (new ShippableOrder (orderRecord.orderId, orderRecord.orderItems .stream (). map (orderItem -> new PackageItem (orderItem.productId, orderItem.itemWeight, orderItem.quantity * orderItem.unitPrice)) .collect (Collectors. toList ()))); }}

Itt állandó jellegűek és visszakeresjük a különböző típusú entitásokat a tartós modellek megfelelő típusba történő átalakításával.

Végül hozzuk létre a modul definícióját:

modul com.baeldung.dddmodules.infrastructure {tranzitív com.baeldung.dddmodules.sharedkernel szükséges; átmeneti com.baeldung.dddmodules.ordercontext; tranzitív com.baeldung.dddmodules.shippingcontext-t igényel; biztosítja a com.baeldung.dddmodules.sharedkernel.events.EventBus szolgáltatást a com.baeldung.dddmodules.infrastructure.events.SimpleEventBus; biztosítja a com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository fájlt a com.baeldung.dddmodules.infrastructure.db.InMemoryOrderStore; biztosítja a com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository fájlt a com.baeldung.dddmodules.infrastructure.db.InMemoryOrderStore; }

Használni a biztosítja azzal záradék, néhány interfész megvalósítását biztosítjuk, amelyeket más modulokban definiáltak.

Ezenkívül ez a modul a függőségek összesítőjeként működik, ezért a transzitív kulcsszó. Ennek eredményeként egy modul, amely az Infrastructure modult igényli, átmenetileg megkapja ezeket a függőségeket.

4.5. Fő modul

Befejezésül definiáljunk egy modult, amely az alkalmazásunk belépési pontja lesz:

modul com.baeldung.dddmodules.mainapp {használja a com.baeldung.dddmodules.sharedkernel.events.EventBus; használja a com.baeldung.dddmodules.ordercontext.service.OrderService; használja a com.baeldung.dddmodules.ordercontext.repository.CustomerOrderRepository; használja a com.baeldung.dddmodules.shippingcontext.repository.ShippingOrderRepository; használja a com.baeldung.dddmodules.shippingcontext.service.ShippingService; tranzitív com.baeldung.dddmodules.infrastructure szükséges; }

Mivel most állítottunk be tranzitív függőségeket az Infrastruktúra modulhoz, itt nem kell ezeket kifejezetten előírnunk.

Másrészt felsoroljuk ezeket a függőségeket a használ kulcsszó. A használ záradék utasítja ServiceLoader, amelyet a következő fejezetben fedezünk fel, hogy ez a modul ezeket az interfészeket akarja használni. Azonban, nem igényli, hogy a megvalósítások elérhetők legyenek a fordítás alatt.

5. Az alkalmazás futtatása

Végül majdnem készen állunk az alkalmazásunk elkészítésére. Mavenet felhasználjuk projektünk felépítéséhez. Ez sokkal megkönnyíti a modulokkal való munkát.

5.1. Projekt felépítése

Projektünk öt modult és a szülő modult tartalmaz. Vessünk egy pillantást a projekt felépítésére:

ddd-modules (a gyökérkönyvtár) pom.xml | - infrastruktúra | - src | - main | - java module-info.java | - com.baeldung.dddmodules.infrastructure pom.xml | - mainapp | - src | - main | - java module-info.java | - com.baeldung.dddmodules.mainapp pom.xml | - ordercontext | - src | - main | - java module-info.java | --com.baeldung.dddmodules.ordercontext pom.xml | - sharedkernel | - src | - main | - java module-info.java | - com.baeldung.dddmodules.sharedkernel pom.xml | - shippingcontext | - src | - main | - java module-info.java | - com.baeldung.dddmodules.shippingcontext pom.xml

5.2. Fő alkalmazás

Mostanra a fő alkalmazás kivételével minden megvan, tehát definiáljuk a sajátunkat fő- módszer:

public static void main (String args []) {Térkép container = createContainer (); OrderService orderService = [OrderService] container.get (OrderService.class); ShippingService shippingService = (ShippingService) container.get (ShippingService.class); shippingService.listenToOrderEvents (); CustomerOrder customerOrder = új CustomerOrder (); int orderId = 1; customerOrder.setOrderId (orderId); List orderItems = new ArrayList (); orderItems.add (új OrderItem (1, 2, 3, 1)); orderItems.add (új OrderItem (2, 1, 1, 1)); orderItems.add (új OrderItem (3, 4, 11, 21)); customerOrder.setOrderItems (orderItems); customerOrder.setPaymentMethod ("PayPal"); customerOrder.setAddress ("Teljes cím itt"); orderService.placeOrder (customerOrder); if (orderId == shippingService.getParcelByOrderId (orderId) .get (). getOrderId ()) {System.out.println ("A megrendelést sikeresen feldolgoztuk és szállítottuk"); }}

Röviden beszéljük meg fő módszerünket. Ebben a módszerben egy egyszerű vevői megrendelés folyamatát szimuláljuk korábban meghatározott szolgáltatások felhasználásával. Eleinte három tételből hoztuk létre a rendelést, és megadtuk a szükséges szállítási és fizetési információkat. Ezután benyújtottuk a megrendelést, és végül ellenőriztük, hogy sikeresen elküldtük-e és feldolgoztuk-e.

De hogyan kaptuk meg az összes függőséget és miért createContainer módszer visszatér Térkép<> Tárgy>? Vizsgáljuk meg közelebbről ezt a módszert.

5.3. Függőség befecskendezése a ServiceLoader használatával

Ebben a projektben nincsenek tavaszi IoC-függőségek, ezért alternatívaként a ServiceLoader API a szolgáltatások megvalósításainak felfedezéséhez. Ez nem újdonság - a ServiceLoader Maga az API a Java 6 óta létezik.

A statikus meghívásával megszerezhetünk egy betöltő példányt Betöltés módszerei ServiceLoader osztály. A Betöltés metódus a Iterálható típusú, hogy iterálhassunk a felfedezett megvalósítások felett.

Most alkalmazzuk a betöltőt függőségeink megoldására:

nyilvános statikus térkép createContainer () {EventBus eventBus = ServiceLoader.load (EventBus.class) .findFirst (). get (); CustomerOrderRepository customerOrderRepository = ServiceLoader.load (CustomerOrderRepository.class) .findFirst (). Get (); ShippingOrderRepository shippingOrderRepository = ServiceLoader.load (ShippingOrderRepository.class) .findFirst (). Get (); ShippingService shippingService = ServiceLoader.load (ShippingService.class) .findFirst (). Get (); shippingService.setEventBus (eventBus); shippingService.setOrderRepository (shippingOrderRepository); OrderService orderService = ServiceLoader.load (OrderService.class) .findFirst (). Get (); orderService.setEventBus (eventBus); orderService.setOrderRepository (customerOrderRepository); HashMap tároló = új HashMap (); container.put (OrderService.class, orderService); container.put (ShippingService.class, shippingService); visszatérő konténer; }

Itt, hívjuk a statikusat Betöltés módszer minden szükséges felülethez, amely minden alkalommal új betöltőpéldányt hoz létre. Ennek eredményeként nem tárolja a már megoldott függőségeket gyorsítótárba, ehelyett minden alkalommal új példányokat hoz létre.

A szolgáltatáspéldányok általában kétféleképpen hozhatók létre. Vagy a szolgáltatás implementációs osztályának rendelkeznie kell nyilvános no-arg konstruktorral, vagy statikusat kell használnia szolgáltató módszer.

Ennek következtében szolgáltatásaink többségének nincs argumentum nélküli konstruktora és szetter módszere a függőségekre. De, mint már láttuk, a InMemoryOrderStore osztály két interfészt valósít meg: CustomerOrderRepository és ShippingOrderRepository.

Ha azonban ezeket az interfészeket a Betöltés metódus esetén a InMemoryOrderStore. Ez nem kívánatos viselkedés, ezért használjuk a szolgáltató method technika a példány gyorsítótárazásához:

public class InMemoryOrderStore megvalósítja a CustomerOrderRepository, a ShippingOrderRepository {private volatile static InMemoryOrderStore instance = new InMemoryOrderStore (); public static InMemoryOrderStore szolgáltató () {return példány; }}

A Singleton mintát alkalmaztuk a InMemoryOrderStore osztályba, és adja vissza a szolgáltató módszer.

Ha a szolgáltató kijelenti a szolgáltató módszer, majd a ServiceLoader ezt a módszert hívja meg egy szolgáltatás példányának megszerzésére. Ellenkező esetben megpróbál létrehozni egy példányt a No-arguments konstruktor segítségével a Reflection segítségével. Ennek eredményeként megváltoztathatjuk a szolgáltató mechanizmusát anélkül, hogy befolyásolnánk createContainer módszer.

És végül megoldott függőségeket biztosítunk a szolgáltatásokhoz a beállítókon keresztül, és visszaküldjük a konfigurált szolgáltatásokat.

Végül futtathatjuk az alkalmazást.

6. Következtetés

Ebben a cikkben néhány kritikus DDD-fogalmat taglaltunk: Bounded Context, Ubiquitous Language és Context Mapping. Míg a rendszert Bounded Contexts-re osztani, rengeteg előnye van, ugyanakkor nem szükséges ezt a megközelítést mindenhol alkalmazni.

Ezután láttuk, hogyan lehet a Java 9 modulrendszert és a Bounded Contextet együtt használni erősen beágyazott modulok létrehozásához.

Továbbá kitértünk az alapértelmezésre ServiceLoader a függőségek felfedezésének mechanizmusa.

A projekt teljes forráskódja elérhető a GitHub oldalon.


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