DDD - Specification vs Entity (Yourdon/Chen)

Pagina: 1
Acties:

  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Ik zit eigenlijk hiermee:

Stel, je hebt een 'probleem-domein' van bv een online shop. Die winkel heeft Customers. Een Customer kan Orders plaatsen, en een Order bestaat uit één of meer OrderLines.

Stel nu dat een Customer de ‘gold status’ verkrijgt als hij voor 10.000 euro orders heeft geplaatst. In een Domain Driven Design zou je dus logischerwijze dit gedrag in de Customer class definiëren. Dit zou je op deze manier kunnen doen:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Customer
{
    public bool IsGoldCustomer()
    {
         Decimal total = 0;

         foreach( Order o in _orderCollection )
         {
              total += o.OrderAmount;
         }

         return ( total > 10000);

    }
}


Dit zou je dus in eerste instantie kunnen doen. Echter is dit niet echt de aangewezen manier. Om te bepalen of een klant ‘Gold’ is, zou je dus alle Orders en alle Orderlines voor die klant uit de DB moeten trekken. Als OrderLines lazy loaded is, betekent dit dus dat er voor ieder Order object een query moet uitgevoerd worden…. Niet echt bevorderend voor de performance als je zo een aantal Orders hebt voor een Customer, of als je dit moet doen voor een paar Customer objecten. Je trekt ook een aantal objecten uit de DB die je misschien enkel nodig hebt om deze test te doen.

Het is dus beter om deze logica uit het Customer object te halen, en het in een ‘Specification’ te stoppen.
Die ‘Specification’ kan gewoon gebruik maken van SQL om het totale order-bedrag voor die klant op te halen. De logica blijft ook binnen de domein-laag zitten.
code:
1
2
3
4
5
6
7
8
9
public class CustomerGoldStatusSpecification
{
      public bool IsSatisfied( Customer c )
      {
            Decimal amount = customerRepository.GetOrderTotalForCustomer (c.Id);

            return ( amount > 10000);
      }
}

Dit is heel wat beter. De customerRepository voert gewoon één query uit die het OrderTotaal voor de gewenste klant ophaalt. Geen verspilling van resources, geen n queries die moeten uitgevoerd worden.

Echter…. Als je alle logica die een beetje complex is, uit de domain entities haalt, wat blijft er dan nog over van die entities ? Worden het dan niet gewoon ‘domme’ entiteiten zonder logica, en zit al je logica dan niet in specifications ? Wat is het verschil dan tussen Domain Driven Design en de Entity aanpak van Yourdon ?

https://fgheysels.github.io/


  • EfBe
  • Registratie: Januari 2000
  • Niet online
3 typen BL:
1) field focussed: ID > 0
2) entity focussed: OrderDate <= ShippingDate
3) cross-entity focussed: Customer.GoldStatus = meer dan x orders in de laatste N maanden.

1 en 2 kunnen prima in een entity, 3 wordt wat lastig want je refereert aan meerdere entities en dus waar plaats je deze logica, zoals je zelf aangeeft. Ikzelf plaats deze altijd buiten een entity, omdat een object erbuiten logischer is zoals jij ook aangeeft. Dat houdt niet in dat de entities dom zijn, ze focussen alleen op wat henzelf aangaat.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Idd.

Wat ik misschien wel kan doen, is dit:
code:
1
2
3
4
5
6
7
8
9
10
public class Customer
{

    public bool IsGoldCustomer
    {
          CustomerGoldStatusSpecification s = new CustomerGoldStatusSpecification();
          return s.IsSatisfied (this);
    }

}

https://fgheysels.github.io/


  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 02-04 13:13

pjvandesande

GC.Collect(head);

Ik zou de login wel in de Customer class houden, het is immers een flag die bij de Customer hoort. Om hem los te koppelen vind ik nogal heftig, zeker als je in een situatie zit waar je nog meer flags krijgt. Dan zou je dus tig Specification classes krijgen, niet gewenst.

De aanpak waar ik nog steeds tevreden over ben is een combinatie van bijden, wat je zelf ook al aangeeft.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Customer
{
    public bool IsGold
    {
        get
        {
            return CustomerRepository.GetGoldStatus( this );
        }
    }
    
    public bool IsSilver
    {
        get
        {
            return CustomerRepository.GetSilverStatus( this );
        }
    }
    
    public bool IsBronse
    {
        get
        {
            return CustomerRepository.GetBronse( this );
        }
    }
}

}

  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Met jouw manier zit je dan met business logic in je repository. Dat wil je niet; wat ik zou doen, is dit:

code:
1
2
3
4
5
6
7
8
9
10
11
class Customer
{
    public CustomerStatus  Status
    {
         get
          {
                CustomerStatusSpec spec = new CustomerStatusSpec();
                return spec.GetStatus(this);
          }
    }
}

Waarbij de CustomerStatusSpec class dan eigenlijk de business-logic bevat, die gaat naar de repository en vraagt het order-totaal van die klant:
code:
1
2
3
4
5
6
7
8
9
10
11
12
public CustomerStatus GetStatus(Customer c )
{
     decimal orderTotal = customerRepository.GetOrderTotalForCustomer(c.Id);

     if( orderTotal > 5000 && orderTotal <= 10000 )
         return CustomerStatus.Bronze;
     else if( orderTotal > 10000 && orderTotal <= 15000 )
           return CustomerStatus.Silver;
     else if( orderTotal > 15000 )
            return CustomerStatus.Gold;
      else return CustomerStatus.Normal;
}


Dan vermijd je ook direct die 3 properties, want een Customer kan niet Gold, Silver en Bronze zijn op hetzelfde moment.

https://fgheysels.github.io/


  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Of, misschien kan je in bepaalde gevallen wel die customer-repository rechtstreeks vanuit je Customer object aanspreken, en in je Customer object dan zelf de logica gaan hebben:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Customer
{
    public CustomerStatus Status
    {
         get 
         {
               CustomerRepository rep = new ...
               decimal d = rep.GetOrderTotalForCustomer (this.Id);

               if( d > 5000 && d <= 10000 )
                  ....
         }
    }
}

https://fgheysels.github.io/


  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 02-04 13:13

pjvandesande

GC.Collect(head);

whoami schreef op donderdag 09 maart 2006 @ 10:51:
Of, misschien kan je in bepaalde gevallen wel die customer-repository rechtstreeks vanuit je Customer object aanspreken, en in je Customer object dan zelf de logica gaan hebben:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Customer
{
    public CustomerStatus Status
    {
         get 
         {
               CustomerRepository rep = new ...
               decimal d = rep.GetOrderTotalForCustomer (this.Id);

               if( d > 5000 && d <= 10000 )
                  ....
         }
    }
}
Dit is inderdaad de manier die ik vooral gebruik. Waarom zou je de Repository niet vanuit je Customer class willen aanroepen?

  • whoami
  • Registratie: December 2000
  • Laatst online: 07-04 22:26
Die repository kan je wel vanuit je customer class aanroepen, maar je wilt geen Business Logica in je repository hebben, zoals je in je eerdere voorbeeld aanhaalde.
Daar ga je in je repository gaan bepalen of een customer gold, silver of bronze is.

https://fgheysels.github.io/


  • pjvandesande
  • Registratie: Maart 2004
  • Laatst online: 02-04 13:13

pjvandesande

GC.Collect(head);

whoami schreef op donderdag 09 maart 2006 @ 12:09:
Die repository kan je wel vanuit je customer class aanroepen, maar je wilt geen Business Logica in je repository hebben, zoals je in je eerdere voorbeeld aanhaalde.
Daar ga je in je repository gaan bepalen of een customer gold, silver of bronze is.
Hangt er een beetje vanaf of je dit als SP in je database regeld. Dan zou ik dit in de Repository stoppen, helaas is dit bij ons voor de flags het geval. Daar zit dus Business Logic in de database, maar als je dat niet hebt inderdaad zou ik de logic in je Customer class zelf houden.
Pagina: 1