Domain model and related data (anemic region model)

I am currently working with ASP.NET Core 1.0 using Entity Framework Core. I have some complicated calculations with data from a database, and I'm not sure how to build the right architecture using Dependency Injection without creating an anemic domain model ( http://www.martinfowler.com/bliki/AnemicDomainModel.html )

(simplified) Example:

I have the following objects:

public class Project { public int Id {get;set;} public string Name {get;set;} } public class TimeEntry { public int Id {get;set;} public DateTime Date {get;set;} public int DurationMinutes {get;set;} public int ProjectId {get;set;} public Project Project {get;set;} } public class Employee { public int Id {get;set;} public string Name {get;set;} public List<TimeEntry> TimeEntries {get;set;} } 

I want to do some complicated calculations to calculate the monthly timeshare. Since I cannot access the database in the Employee entity, I compute the TimeSheet in the EmployeeService .

 public class EmployeeService { private DbContext _db; public EmployeeService(DbContext db) { _db = db; } public List<CalculatedMonth> GetMonthlyTimeSheet(int employeeId) { var employee = _db.Employee.Include(x=>x.TimeEntry).ThenInclude(x=>x.Project).Single(); var result = new List<CalculatedMonth>(); //complex calculation using TimeEntries etc here return result; } } 

If I want to get a TimeSheet, I need to enter an EmployeeService and call GetMonthlyTimeSheet .

So - I get many GetThis () and GetThat () methods inside my service, although these methods fit perfectly in the Employee class.

What I want to achieve is something like:

 public class Employee { public int Id {get;set;} public string Name {get;set;} public List<TimeEntry> TimeEntries {get;set;} public List<CalculatedMonth> GetMonthlyTimeSheet() { var result = new List<CalculatedMonth>(); //complex calculation using TimeEntries etc here return result; } } public IActionResult GetTimeSheets(int employeeId) { var employee = _employeeRepository.Get(employeeId); return employee.GetTimeSheets(); } 

... but for this I need to make sure that the TimeEntries list is populated from the database (EF Core does not support lazy loading). I do not want. Include (x => y) everything in every request, because sometimes I just need an employee name without time, and this will affect application performance.

Can someone tell me how to do this correctly?

Edit: One of the possibilities (from the comments of the first answer):

 public class Employee { public int Id {get;set;} public string Name {get;set;} public List<TimeEntry> TimeEntries {get;set;} public List<CalculatedMonth> GetMonthlyTimeSheet() { if (TimeEntries == null) throw new PleaseIncludePropertyException(nameof(TimeEntries)); var result = new List<CalculatedMonth>(); //complex calculation using TimeEntries etc here return result; } } public class EmployeeService { private DbContext _db; public EmployeeService(DbContext db) { _db = db; } public Employee GetEmployeeWithoutData(int employeeId) { return _db.Employee.Single(); } public Employee GetEmployeeWithData(int employeeId) { return _db.Employee.Include(x=>x.TimeEntry).ThenInclude(x=>x.Project).Single(); } } public IActionResult GetTimeSheets(int employeeId) { var employee = _employeeService.GetEmployeeWithData(employeeId); return employee.GetTimeSheets(); } 
+5
source share
2 answers

Do not try to solve problems with your units. Your units are designed to process commands and protect invariants . They form a boundary of consistency around a dataset.

Is the Employee object responsible for protecting the integrity of employee schedules? If this is not the case, then this data does not belong to the Employee class.

Lazy-load can be thin for CRUD models, but usually we consider the anti-pattern when designing aggregates, because they should be as small and solid as possible.

Do you make business decisions based on calculated results from schedules? Are there any invariants for protection? Does it matter if a decision was made regarding outdated schedule data? If the answer to these questions is not , then your calculation is nothing more than a request.

The placement of requests in the service facilities is excellent. These service objects can even live outside the domain model (for example, at the application level), but a strict rule does not follow. In addition, you can load several aggregates to access the required data for processing calculations, but it is usually better to go directly to the database. This allows you to better separate your reading and writing (CQRS).

+3
source

If I understand your question correctly, you can use the service injection trick in your entities that help him do this work, for example:

 public class Employee() { public object GetTimeSheets(ICalculatorHelper helper) { } } 

Then, in your service, which contains employees, you will get it in the constructor and go to the employee class for calculations. This service may be a facade method, for example. to get all the data and complete the initialization or what you really need.

As for TimeEntries, you can get them using a function like this:

 private GetTimeEntries(ICalculationHelper helper) { if (_entries == null) { _entries = helper.GetTimeEntries(); } return _entries; } 

It depends on the strategy of the caching strategy, etc., if this template suits you.

Personally, it’s pretty easy for me to work with anemic classes and have a lot of business logic in services. I put some objects, for example, for example. computing FullName from FirstName and LastName. Usually things that are not related to other services. Although this is a matter of preference.

0
source

Source: https://habr.com/ru/post/1260126/


All Articles