7

Projektuję system, który ma prosty obiekt domeny wspieranej przez Entity Framework, który ma pola, które muszę aktualizować w oparciu o szereg reguł - chcę je wdrożyć rządzi progresywnie (w stylu zwinnym) i jak używam EF, sceptycznie podchodzę do umieszczania każdej reguły w obiekcie domeny. Jednak chcę uniknąć pisania "kodu proceduralnego" i używania anemicznych modeli domen. To wszystko musi być również testowalne.Jak uniknąć anemicznego modelu domenowego z logiką biznesową w postaci reguł

Jako przykład, obiekt jest:

class Employee { 
private string Name; 
private float Salary; 
private float PensionPot; 
private bool _pension; 
private bool _eligibleForPension; 

} 

muszę zbudować reguły takie jak „jeśli wynagrodzenie jest wyższe niż 100000 i _eligibleForPension jest fałszywa wtedy ustawić _eligibleForPension jako prawdziwy” i „jeśli _pension jest prawdziwe wtedy ustaw _eligibleForPension jako true ".

Istnieje około 20 takich zasad i szukam porady, czy powinny być one wdrożone w klasie pracownika lub w czymś podobnym do klasy EmployeeRules? Moją pierwszą myślą było stworzenie oddzielnej klasy dla każdej reguły dziedziczącej po "Regule", a następnie zastosowanie każdej reguły do ​​klasy Employee, być może przy użyciu wzorca Visitor, ale musiałem ujawnić wszystkie pola regułom, aby to zrobić, aby czuje się źle. Posiadanie każdej reguły na poziomie pracownika również nie jest odpowiednie. W jaki sposób zostanie to wdrożone?

Drugą sprawą jest to, że faktyczni Pracownicy to jednostki Entity Framework, które wspierają DB, więc nie czuję się szczęśliwy dodając logikę do tych "Entities" - szczególnie, gdy muszę kpić z obiektów, testując każdą regułę. Jak mogłem ich wyśmiewać, jeśli mają zasady, które testuję na tym samym obiekcie?

Zastanawiam się, czy użyć AutoMappera, aby przekonwertować na prostszy obiekt domeny przed zastosowaniem reguł, ale następnie samodzielnie zarządzać aktualizacjami w tych polach. Wszelkie porady na ten temat też?

Odpowiedz

7

Jednym ze sposobów jest uczynienie reguł klasami wewnętrznymi Employee. Zaletą tego podejścia jest to, że pola mogą pozostać prywatne. Ponadto, inwokacja zasad mogą być egzekwowane przez samą klasę pracownika, dzięki czemu są one zawsze wywoływana, gdy potrzebne:

class Employee 
{ 
    string id; 
    string name; 
    float salary; 
    float pensionPot; 
    bool pension; 
    bool eligibleForPension; 

    public void ChangeSalary(float salary) 
    { 
     this.salary = salary; 
     ApplyRules(); 
    } 

    public void MakeEligibleForPension() 
    { 
     this.eligibleForPension = true; 
     ApplyRules(); // may or may not be needed 
    } 

    void ApplyRules() 
    { 
     rules.ForEach(rule => rule.Apply(this)); 
    } 

    readonly static List<IEmployeeRule> rules; 

    static Employee() 
    { 
     rules = new List<IEmployeeRule> 
     { 
      new SalaryBasedPensionEligibilityRule() 
     }; 
    } 

    interface IEmployeeRule 
    { 
     void Apply(Employee employee); 
    } 

    class SalaryBasedPensionEligibilityRule : IEmployeeRule 
    { 
     public void Apply(Employee employee) 
     { 
      if (employee.salary > 100000 && !employee.eligibleForPension) 
      { 
       employee.MakeEligibleForPension(); 
      } 
     } 
    } 
} 

Jednym z problemów jest to, że klasa pracownik musi zawierać wszystkie implementacje reguły. Nie jest to poważny problem, ponieważ zasady zawierają logikę biznesową związaną z emeryturami pracowniczymi, więc należą do siebie.

+0

To wygląda jak by to zrobić sztuczka. Chociaż podałem przykład z prywatnymi polami, co jest moim pożądanym projektem, EF ma właściwości publiczne, więc nie musiałbym używać klas wewnętrznych, jeśli uzyskałem bezpośredni dostęp do klasy Pracownicy. Mam zamiar pozostawić pytanie otwarte na trochę w nadziei, że ktoś może odpowiedzieć na pytanie, w tym części EF. Dzięki! – PCurd

+0

Zbudowałem model demonstracyjny systemu, stosując nieco zmodyfikowane podejście - nie mam reguł jako klas wewnętrznych - i działa dobrze. Wiem dla tego systemu, że publiczne udostępnienie pól nie jest straszną zbrodnią, ale mógłbym spróbować ponownie używając wewnętrznych klas, aby zobaczyć jak to działa. Dzięki za pomoc. – PCurd

4

Reguły biznesowe to zazwyczaj interesujący temat. Z pewnością może istnieć różnica między niezmiennikiem agregatu/jednostki a regułą biznesową. Reguły biznesowe mogą wymagać danych zewnętrznych i nie zgadzam się z regułą zmieniającą agregat/encję.

Powinieneś pomyśleć o wzorze specyfikacji dla reguł. Reguła powinna w zasadzie po prostu zwrócić, czy była zepsuta, czy nie, z ewentualnym opisem rodzajów.

W twoim przykładzie SalaryBasedPensionEligibilityRule, używany przez eulerfx, może potrzebować trochę PensionThreshold. Ta reguła naprawdę wygląda bardziej jak zadanie, ponieważ reguła tak naprawdę nie sprawdza ważności encji.

Proponuję zatem, aby reguły były mechanizmem decyzyjnym, a zadania polegały na zmianie stanu.

Powiedział, że jest to prawdopodobnie chcą zwrócić się do podmiotu o poradę tutaj ponieważ nie chce narażać stan:

public class Employee 
{ 
    float salary; 
    bool eligibleForPension; 

    public bool QualifiesForPension(float pensionThreshold) 
    { 
     return salary > pensionThreshold && !eligibleForPension; 
    } 

    public void MakeEligibleForPension() 
    { 
     eligibleForPension = true; 
    } 
} 

to kije z idei separacji polecenia/zapytania.

Jeśli budujesz bezpośrednio z obiektów ORM i nie chce lub nie może obejmować cały zachowanie wtedy, że jest OK --- ale z pewnością pomoże :)

+0

Ciekawe podejście i lepiej pasuje do mantry "zachowaj logikę z obiektem". Skończyłbyś z każdą logiką biznesową wdrażaną na obiekcie Employee, a następnie kolejny obiekt odpowiedzialny za pytanie "czy spełniasz warunki tej reguły?" Następnie uruchom tę aktualizację "? Widzę, że staje się to bardzo skomplikowane, ale także zachowuje całą logikę dołączoną do obiektu Employee. Chyba zależy to od tego, czy pożądane jest posiadanie wielu warunków i aktualizacji na obiekcie Pracownika. Czy możesz podać przykład, jak wyglądałby inny obiekt porządkujący aktualizacje? – PCurd

+0

"Czy możesz podać przykład, jak wyglądałby inny obiekt porządkujący aktualizacje?" --- Ja * naprawdę * nie rozumiem tego :) --- Czy możesz powtórzyć swoje pytanie? –

+0

Przepraszamy! Musiałaby istnieć funkcja gdzieś, która "jeśli employee.QifiifiesForPension (100000) then employee.MakeEligibleForPension();" jak wyglądałaby relacja między tą pracą a Pracownikiem - czy byłaby to osobna klasa, a jeśli tak, to jak by ona wyglądała? – PCurd