ITHub

Vérszegény vs. gazdag domain modell: mi a különbség, melyiket használjuk?

Vérszegény vs. gazdag domain modell: mi a különbség, melyiket használjuk?
Farkas Gábor
Farkas Gábor
| ~4 perc olvasás

A szoftverarchitektúrák témakörében a fejlesztői közösség egyik visszatérő vitája az ún. vérszegény (anemic) és gazdag (rich) domain modell körül forog. Míg sok neves szakember a vérszegény modellt egyenesen ellenpéldaként hozza, az ORM-ek és a funkcionális megközelítés miatt ismét előtérbe került, és sokan inkább erre esküsznek. Ebben a posztban áttekintjük mi is a két megközelítés közt a különbség, és megvizsgáljuk mindkettő előnyeit és hátrányait is.

A két modell alapvetően a szoftverünk entitásainak viselkedésének implementálásban különbözik. Ezt legegyszerűbben talán egy példán keresztül lehet bemutatni.

A vérszegény domain modell

Képzeljünk el egy egyszerű (és unalomig ismételt) alkalmazást, amelyben egy webshopot implementálunk. Szükségünk lesz egy, a kosarunkat reprezentáló osztályra (ShoppingCart), valamint egy, a benne lévő termékeket reprezentálóra (ShoppingCartItem). Szeretnénk, ha a kosárhoz hozzá tudnánk adni új termékeket (de legfeljebb 5 termék lehet benne), illetve kell egy, a kosár teljes árát megadó funkció is. A vérszegény modellben ezek valahogy így néznének ki (pszeudókód, bocsi):

public class ShoppingCart {

  public List Items { get; set; }
  
}

public class ShoppingCartItem {

  public string Name { get; set; }
  public int Price { get; set; }
  public int Quantity { get; set; }
  
}

Mi sem egyszerűbb, gyakran találkozunk is hasonlóval, ha valamilyen ORM-et használunk. Azonban valami szembetűnően hiányzik, ez pedig az osztályok viselkedése.

Ebben a modellben az osztályok egyetlen dolga az adatok tárolása, láthatóan semmiféle business logikát nem valósítanak meg. Ebben a két osztályban nyoma sincs a kért funkcióknak — hova kerülnek hát?

Az üzleti logika implementációja itt valamilyen Manager/Service/Util végződésű osztályokban található, kinek mi a szimpatikusabb. Ez alapján így nézhetne ki például egy ShoppingCartService osztály:

public class ShoppingCartService {

  public int GetTotalPriceOfShoppingCart(ShoppingCart shoppingCart) {
    int totalPrice;
	
    for(item in shoppingCart.Items) {
	  totalPrice += item.Price;
	}
	
	return totalPrice;
  }
  
  public void AddItemToShoppingCart(ShoppingCart shoppingCart, ShoppingCartItem item) {
    if (shoppingCart.Items.Count >= 5) {
	  throw new Exception();
	} else {
	  shoppingCart.Items.Add(item);
	}
  }
	
}

Látható, maguk az entitások "buták", semmit sem tudnak a business szabályokról és az alkalmazást vezérlő logikáról.

Mondhatjuk, hogy ez a megvalósítás egyszerű és jól érthető, és belegondolva ez igaz is, az egyetlen "apró" probléma, hogy bár osztályokkal dolgozunk, ez nem igazán nevezhető objektum-orientált programozásnak, sokkal inkább procedurális paradigmáról beszélhetünk OO köntösben. Könnyen látható, hogy egyetlen alapvető OO elv sem érvényesül, kezdve a beágyazástól az információ-elrejtésén át a viselkedés és az adatok összefonódásáig.

Egy vérszegény modellben semmi sem garantálja, hogy az entitásaink állapota bármely pillanatban érvényes, hiszen az attribútumaikat szabadon változtathatjuk. Egy másik érdekes tulajdonság, hogy a service osztályokban lévő logika állapot nélküli és független az entitás-objektumoktól.

A gazdag domain modell

Valósítsuk meg ugyanezt a primitív példát egy gazdag domain modellel.

public class ShoppingCart {

  private List Items;
  
  public int GetTotalPrice() {
    int totalPrice;
	
    for(item in Items) {
	  totalPrice += item.Price;
	}
	
	return totalPrice;
  }
  
  public void AddItem(ShoppingCartItem item) {
    if (Items.Count >= 5) {
	  throw new Exception();
	} else {
	  Items.Add(item);
	}
  }
  
}

Látható, hogy a service osztály eltűnt, és minden logika az entitáson belül van megvalósítva. Ez garantálni fogja, hogy a ShoppingCart entitás sosem kerül érvénytelen állapotba (szebben fogalmazva védi az invariánsait), és ebben az esetben valódi OO kódról beszélhetünk.

Akkor most melyiket használjuk?

Ez alapján az olvasóban talán felmerülhet a kérdés, hogy szól-e egyáltalán érv a vérszegény verzió mellett. Ebben a primitív példában nyilván nem igazán, azonban fontos megjegyezni, hogy bár a gazdag modellben az entitások mindig érvényes állapotban vannak, nem ez egy szoftverrendszer egyetlen kívánatos tulajdonsága, ráadásul ára is van, mégpedig az, hogy a komplexitás növekedésével a rendszerünk egyre szorosabban csatolttá és egyre kevésbé flexibilissé válik. Ez bizonyos esetekben ronthatja a kód tesztelhetőségét (nehezebb test seameket létrehozni) és a karbantarthatóságát is.

Egyes kritikusok szerint összetett rendszereknél a gazdag megközelítés az SRP-t (Single Responsibility Principle, azaz egy osztálynak csak egy felelőssége legyen, de azt csinálja jól) is megsértheti, hiszen megfelelően komplex logika esetén az entitásosztályok komoly méretet ölthetnek. Végül érdemes hozzátenni, hogy bizonyos ORM-eknél nincs is választásunk, hiszen ezek az eszközök néha szigorú szabályokat szabnak az entitásosztályok architektúráját illetően (pl. léteznie kell paraméter nélküli konstruktornak, stb.).

Ahogy az legtöbbször lenni szokott, valódi nyertes ebben az esetben sincs (bár továbbra is több érv szól a gazdag domain modell mellett), minden alkalmazásnál külön kell mérlegelnünk, hogy vérszegény vagy gazdag adatmodellt kívánunk alkalmazni, figyelembe véve az alkalmazás méretét és az üzleti szabályok összetettségét.