Megosztás a következőn keresztül:


Mikroszolgáltatás-tartománymodell implementálása a .NET-tel

Jótanács

Ez a tartalom egy részlet a '.NET Microservices Architecture for Containerized .NET Applications' című eBook-ból, amely elérhető a .NET Docs oldalon, vagy ingyenesen letölthető PDF formátumban, amely offline módban is olvasható.

.NET mikroszolgáltatások architektúrája konténerizált .NET alkalmazásokhoz e-könyv borító miniatűr.

Az előző szakaszban a tartománymodellek tervezésének alapvető tervezési alapelveit és mintáit ismertették. Most itt az ideje, hogy megismerje a tartománymodell megvalósításának lehetséges módjait .NET (egyszerű C#-kód) és EF Core használatával. A tartománymodell egyszerűen a kódból lesz összeállítva. Ez csak az EF Core-modell követelményeit fogja kielégíteni, de az EF-hez való tényleges függőségeket nem. Nem szabad szigorú függőségekkel vagy hivatkozásokkal rendelkeznie az EF Core-ra vagy bármely más ORM-ra a tartománymodellben.

Tartománymodell-struktúra egyéni .NET Standard kódtárban

Az eShopOnContainers referenciaalkalmazáshoz használt mappaszervezet az alkalmazás DDD-modelljét mutatja be. Előfordulhat, hogy egy másik mappaszervezet pontosabban közli az alkalmazáshoz választott tervezési lehetőségeket. Ahogy a 7–10. ábrán látható, a rendelési tartománymodellben két aggregátum található, a rendelés összesítése és a vevői aggregátum. Minden összesítés tartományi entitások és értékobjektumok egy csoportja, bár az összesítés egyetlen tartományi entitásból (az összesítő gyökér- vagy gyökérentitásból) is állhat.

Képernyőkép az Ordering.Domain projektről a Megoldáskezelőben.

Az Ordering.Domain projekt Megoldáskezelő nézete, amelyen a BuyerAggregate és az OrderAggregate mappákat tartalmazó AggregatesModel mappa látható, amelyek mindegyike tartalmazza az entitásosztályokat, az értékobjektum-fájlokat és így tovább.

7–10. ábra. Tartománymodell-struktúra az eShopOnContainersben található rendelési mikroszolgáltatáshoz

Emellett a tartománymodell rétege tartalmazza azokat az adattárszerződéseket (interfészeket), amelyek a tartománymodell infrastruktúrakövetelményei. Más szóval ezek az interfészek azt fejezik ki, hogy milyen adattárakat és milyen módszereket kell implementálnia az infrastruktúrarétegnek. Kritikus fontosságú, hogy az adattárak implementációja a tartománymodell-rétegen kívülre kerüljön az infrastruktúraréteg könyvtárában, így a tartománymodell-réteget nem "szennyezi" az API vagy az infrastruktúra-technológiák osztályai, például az Entity Framework.

Olyan SeedWork-mappát is láthat, amely egyéni alaposztályokat tartalmaz, amelyeket a tartományi entitások és értékobjektumok alapjaként használhat, így nem rendelkezik redundáns kóddal az egyes tartományok objektumosztályaiban.

Struktúraaggregátumok egyéni .NET Standard könyvtárban

Az aggregátum a tranzakciós konzisztenciának megfelelően csoportosított tartományobjektumok csoportja. Ezek az objektumok lehetnek entitáspéldányok (amelyek közül az egyik az összesítő gyökér- vagy gyökérentitás), valamint további értékobjektumok.

A tranzakciós konzisztencia azt jelenti, hogy az aggregátumok garantáltan konzisztensek és naprakészek lesznek egy üzleti művelet végén. A mikroszolgáltatás-tartománymodellt rendelő eShopOnContainers rendelési összesítése például a 7–11. ábrán látható módon áll össze.

Képernyőkép az OrderAggregate mappáról és osztályairól.

Az OrderAggregate mappa részletes nézete: Address.cs értékobjektum, az IOrderRepository egy adattár-felület, Order.cs egy összesítő gyökér, OrderItem.cs gyermek entitás, OrderStatus.cs pedig enumerálási osztály.

Ábra 7–11.. A rendelés összesítése a Visual Studio-megoldásban

Ha bármelyik fájlt összesítő mappában nyitja meg, láthatja, hogyan van megjelölve egyéni alaposztályként vagy interfészként, például entitás- vagy értékobjektumként a SeedWork mappában implementálva.

Tartományi entitások implementálása POCO-osztályokként

A tartománymodellt a .NET-ben a tartományentitások implementálására szolgáló POCO-osztályok létrehozásával valósíthatja meg. Az alábbi példában az Order osztály entitásként és összesítő gyökérként is definiálva van. Mivel az Order osztály az Entitás alaposztályból származik, újra felhasználhatja az entitásokhoz kapcsolódó közös kódot. Ne feledje, hogy ezeket az alaposztályokat és interfészeket Ön határozza meg a tartománymodell-projektben, tehát ez az Ön kódja, nem pedig egy OLYAN ORM-ből származó infrastruktúrakód, mint az EF.

// COMPATIBLE WITH ENTITY FRAMEWORK CORE 5.0
// Entity is a custom base class with the ID
public class Order : Entity, IAggregateRoot
{
    private DateTime _orderDate;
    public Address Address { get; private set; }
    private int? _buyerId;

    public OrderStatus OrderStatus { get; private set; }
    private int _orderStatusId;

    private string _description;
    private int? _paymentMethodId;

    private readonly List<OrderItem> _orderItems;
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;

    public Order(string userId, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
            string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null)
    {
        _orderItems = new List<OrderItem>();
        _buyerId = buyerId;
        _paymentMethodId = paymentMethodId;
        _orderStatusId = OrderStatus.Submitted.Id;
        _orderDate = DateTime.UtcNow;
        Address = address;

        // ...Additional code ...
    }

    public void AddOrderItem(int productId, string productName,
                            decimal unitPrice, decimal discount,
                            string pictureUrl, int units = 1)
    {
        //...
        // Domain rules/logic for adding the OrderItem to the order
        // ...

        var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);

        _orderItems.Add(orderItem);

    }
    // ...
    // Additional methods with domain rules/logic related to the Order aggregate
    // ...
}

Fontos megjegyezni, hogy ez egy POCO-osztályként implementált tartományi entitás. Nincs közvetlen függősége az Entity Framework Core-ra vagy bármely más infrastruktúra-keretrendszerre. Ez az implementáció úgy van, ahogyan azt a DDD elvek szerint kell, csupán egy C# kód, amely egy doménmodellt valósít meg.

Emellett az osztály egy IAggregateRoot nevű felülettel van díszítve. Ez az interfész egy üres felület, más néven jelölőfelület, amelyet csak arra használnak, hogy jelezze, hogy ez az entitásosztály egyben összesített gyökér is.

A jelölőfelületet néha antimintának tekintik; ez azonban egy osztály megjelölésének is tiszta módja, különösen akkor, ha ez a felület fejlődik. Egy attribútum lehet a másik lehetőség a jelölő számára, de gyorsabban láthatja az alaposztályt (Entity) az IAggregate interfész mellett, ahelyett, hogy egy összesítő attribútumjelölőt helyezne az osztály fölé. Mindenképpen preferenciákról van szó.

Az összesítő gyökér azt jelenti, hogy az összesítés entitásainak konzisztenciájához és üzleti szabályaihoz kapcsolódó kód nagy részét metódusként kell implementálni az Order aggregate root osztályban (például AddOrderItem, amikor orderItem objektumot ad hozzá az összesítéshez). Az OrderItems-objektumokat nem szabad egymástól függetlenül vagy közvetlenül létrehozni vagy frissíteni; Az AggregateRoot osztálynak folyamatosan ellenőriznie és konzisztenciáját kell tartania a gyermekentitások frissítési műveleteinek.

Adatok beágyazása a tartományi entitásokban

Az entitásmodellek gyakori problémája, hogy nyilvánosan elérhető listatípusokként teszik közzé a gyűjtemény navigációs tulajdonságait. Ez lehetővé teszi, hogy minden közreműködő fejlesztő módosítsa ezeknek a gyűjteménytípusoknak a tartalmát, ami megkerülheti a gyűjteményhez kapcsolódó fontos üzleti szabályokat, és érvénytelen állapotban hagyhatja az objektumot. A megoldás az, hogy írásvédett hozzáférést biztosítunk a kapcsolódó gyűjteményekhez, és egyértelműen meghatározunk olyan módszereket, amelyekkel a kliensek manipulálhatják azokat.

Az előző kódban vegye figyelembe, hogy számos attribútum írásvédett vagy privát, és csak az osztály metódusai frissíthetők, ezért minden frissítés figyelembe veszi az osztály metódusaiban megadott üzleti tartományváltozókat és logikát.

Például a következő DDD-mintákat követve nem szabad a következőt elvégeznie egyetlen parancskezelő metódusból vagy alkalmazásréteg-osztályból sem (valójában ez lehetetlen lehet):

// WRONG ACCORDING TO DDD PATTERNS – CODE AT THE APPLICATION LAYER OR
// COMMAND HANDLERS
// Code in command handler methods or Web API controllers
//... (WRONG) Some code with business logic out of the domain classes ...
OrderItem myNewOrderItem = new OrderItem(orderId, productId, productName,
    pictureUrl, unitPrice, discount, units);

//... (WRONG) Accessing the OrderItems collection directly from the application layer // or command handlers
myOrder.OrderItems.Add(myNewOrderItem);
//...

Ebben az esetben az Add metódus kizárólag adatok hozzáadására irányuló művelet, közvetlen hozzáféréssel az OrderItems gyűjteményhez. Ezért a gyermek entitásokkal végzett művelethez kapcsolódó tartománylogika, szabályok vagy érvényesítések többsége az alkalmazásrétegben (parancskezelők és webes API-vezérlők) lesz elosztva.

Ha megkerüli az összesítő gyökeret, akkor az összesítő gyökér nem tudja garantálni az invariánsait, az érvényességét vagy a konzisztenciáját. Végül spagettikód vagy tranzakciós szkriptkód lesz.

A DDD-minták követéséhez az entitásoknak nem lehetnek nyilvános beállítóik a tulajdonságaikban. Az entitások változásait explicit metódusokkal kell vezérelni, explicit, mindenütt elterjedt nyelven, az entitásban végrehajtott módosításokkal kapcsolatban.

Ezenkívül az entitáson belüli gyűjteményeknek (például a rendeléselemeknek) írásvédett tulajdonságoknak kell lenniük (az AsReadOnly metódus később lesz ismertetve). Csak az összesített gyökérosztály-metódusokból vagy a gyermekentitási metódusokból frissítheti.

Ahogy az a Rendelés aggregálási gyökérkódjában látható, az összes beállítónak privátnak vagy legalább csak olvashatónak kell lennie kívülről, hogy az entitás adataival vagy gyermek entitásaival kapcsolatos műveleteket az entitás osztályának metódusai révén kell végrehajtani. Ez szabályozott és objektumorientált módon tartja fenn a konzisztenciát a tranzakciós szkriptkód implementálása helyett.

Az alábbi kódrészlet bemutatja az OrderItem objektum Rendelési összesítéshez való hozzáadásának megfelelő módját.

// RIGHT ACCORDING TO DDD--CODE AT THE APPLICATION LAYER OR COMMAND HANDLERS
// The code in command handlers or WebAPI controllers, related only to application stuff
// There is NO code here related to OrderItem object's business logic
myOrder.AddOrderItem(productId, productName, pictureUrl, unitPrice, discount, units);

// The code related to OrderItem params validations or domain rules should
// be WITHIN the AddOrderItem method.

//...

Ebben a kódrészletben az OrderItem objektum létrehozásához kapcsolódó legtöbb ellenőrzés vagy logika az AddOrderItem metódus rendelés-összesítési gyökérének felügyelete alatt lesz, különösen az összesítés más elemeihez kapcsolódó érvényesítések és logika. Előfordulhat például, hogy ugyanazt a termékelemet kapja, mint az AddOrderItem funkció többszöri hívása eredményeként. Ebben a módszerben megvizsgálhatja a termékelemeket, és összevonhatja ugyanazokat a termékelemeket egyetlen OrderItem objektumba több egységből. Emellett, ha eltérő kedvezményösszegek vannak, de a termékazonosító megegyezik, valószínűleg a magasabb kedvezményt alkalmazza. Ez az elv az OrderItem objektum bármely más tartománylogikára vonatkozik.

Emellett az új OrderItem(params) műveletet is az AddOrderItem metódus vezérli és hajtja végre az Order aggregátumgyökérből. Ezért a művelethez kapcsolódó legtöbb logika vagy ellenőrzés (különösen minden, ami hatással van a többi gyermek entitás konzisztenciájára) egyetlen helyen lesz az összesített gyökérben. Ez az összesített gyökérminta végső célja.

Az Entity Framework Core 1.1 vagy újabb verziójának használatakor a DDD-entitások jobban kifejezhetők, mivel lehetővé teszik a tulajdonságok mellett a mezőkre való leképezést is. Ez akkor hasznos, ha gyermek entitások vagy értékobjektumok gyűjteményeit védi. Ezzel a fejlesztéssel tulajdonságok helyett egyszerű privát mezőket használhat, és a mezőgyűjtemény bármilyen frissítését implementálhatja nyilvános metódusokban, és írásvédett hozzáférést biztosíthat az AsReadOnly metóduson keresztül.

A DDD-ben csak az entitás (vagy a konstruktor) metódusaival szeretné frissíteni az entitást az invariánsok és az adatok konzisztenciájának szabályozása érdekében, így a tulajdonságok csak egy get kiegészítővel vannak definiálva. A tulajdonságokat magánmezők is alátámasztják. A privát tagok csak az osztályon belülről érhetők el. Van azonban egy kivétel: az EF Core-nak ezeket a mezőket is be kell állítania (hogy a megfelelő értékekkel visszaadhassa az objektumot).

Tulajdonságok leképezése csak az adatbázistábla mezőinek lekérésével

Az adatbázistáblaoszlopok tulajdonságainak leképezése nem tartományi felelősség, hanem az infrastruktúra és a megőrzési réteg része. Ezt csak azért említjük meg, hogy tisztában legyen az EF Core 1.1 vagy újabb verziójának új képességeivel, amelyek az entitások modellezésével kapcsolatosak. A témakör további részleteit az infrastruktúra és a megőrzés szakasz ismerteti.

Az EF Core 1.0 vagy újabb verziójának használatakor a DbContextben a tulajdonságokat, amelyek csak getterekkel vannak definiálva, össze kell hangolni az adatbázistábla tényleges mezőivel. Ez a PropertyBuilder osztály HasField metódusával történik.

Mezők leképezése tulajdonságok nélkül

Az EF Core 1.1 vagy újabb verziójában a mezőkhöz való oszlopleképezés lehetővé teszi a tulajdonságok használatának elhagyását is. Ehelyett egyszerűen csak oszlopokat rendelhet hozzá egy táblából a mezőkhöz. Ennek gyakori használati esete egy belső állapot privát mezői, amelyekhez nem kell az entitáson kívülről hozzáférni.

Az előző OrderAggregate-kód példában például számos olyan privát mező van, például a _paymentMethodId mező, amelyeknek nincs kapcsolódó tulajdonsága egy beállítóhoz vagy egy getterhez. Ezt a mezőt a rendelés üzleti logikájában is ki lehet számítani, és a rendelés metódusaiból lehet használni, de az adatbázisban is meg kell őrizni. Az EF Core-ban (az 1.1-es verzió óta) tehát le lehet képezni egy olyan mezőt, amely nem rendelkezik kapcsolódó tulajdonsággal az adatbázis egyik oszlopához. Ezt az útmutató Infrastruktúra réteg című szakasza is ismerteti.

További erőforrások