Question

We’ve been working on developing an architecture for a layered application. We are planning to have an ASP.NET MVC presentation layer that serves both mobile browsers and ‘normal’ browsers. That is, we need the same ASP.NET MVC presentation layer to serve up views optimized for mobile browsers if the user navigates to the site on a mobile device, while, at the same time, serve up ‘regular’ views that are optimized for desktop/laptop browsers if the user is using their desktop/laptop computer. At the moment, we’re not planning on supporting native mobile apps, but we may add that capability in the future. Additionally, the architecture will have layers for the domain model - domain entities, domain services, etc., application services, and a data layer using EF code-first POCOs and the repository and unit-of-work patterns – all standard domain model type of design.

However, I’ve been trying to figure out the difference between domain services, application services, and behavior (methods) in domain entities as it applies to our system that we’re developing. Below is a high-level use case in our system for examination.

We have Reports and ReportGroups. There is a many-to-many relationship between these two entities. The DB tables are: Reports <-> Jct_Reports_ReportGroups <-> ReportGroups, with the domain entities looking something like:

Report class :
    public string ReportName { get; set; }
    ...
    public virtual ICollection<ReportGroup> ReportGroups { get; set; }

ReportGroup class :
    public string GroupName { get; set; }
    ...
    public virtual ICollection<Report> Reports { get; set; }

Of course, a user could create a report (without creating a report group), and they could create a report group that contains one or many reports. Also, a user can delete a report. When this happens, a number of things occur. First, we check to see if the report is in any report groups (a single report can be in many different report groups). If the report is found in any report groups, we go through each report group and remove the report from the report group (by deleting a row out of the Jct_Reports_ReportGroups table). Then, for each report group that contained the report to be deleted, we check to see if there are any other reports left in the report group. If there are no other reports in the report group, we also delete the report group (by deleting the row from the ReportGroups table). If there are other reports left in the report group, we don’t delete the report group. If all these operations succeed, we remove the report to be deleted as selected by the user (by deleting the row in the Reports table). Finally, the user is displayed a message saying whether the report delete succeeded or not.

I hope that’s an adequate use case to help understand just a tiny bit of our overall application.

I’ve read somewhere that domain services encapsulate business logic that doesn't naturally fit within a domain object, and are NOT typical CRUD operations, while application services are used by external consumers to talk to your system - if consumers need access to CRUD operations, they would be exposed here. But, I’m just not understanding what methods (business logic) would go in the POCO domain entities, what business logic would be considered domain services and contained in the business layer, and what business logic would be considered an application service contained in an application services layer. So, my main question is: given the use case above, what would be the recommendation on how to break out domain services vs. application services vs. behavior (methods) in domain entities?

Was it helpful?

Solution

That's quite a bit of a question.

I prefer the following non-strict guidelines for myself:

Application service: Imagine we have a set of use cases which describe a user interaction with the system. Let's say we have Login and Cash transfer cases with specific inputs and outputs; also they describe success and fail scenarios. Having that, we in general have an application service specification: I'd expose both LoginResul Login(name, pass) and TransferResult CashTrasnfer(from, to, amount) methods, with more or less the same in's and out's and success/fail behaviour.

It's quite obvious that in order to realize use cases, application service calls BL (but adds security and other app-specific checks).

Domain Service: just as you describe: 'doesn't naturally fit within a domain object'.

Having CashTrasnfer use case, we have to:

  • load account 'from'
  • load account 'to'
  • check 'from' balance' //error could be returned
  • verify accounts access (locked, etc.)//error could be returned
  • withdraw 'amount' from 'from'
  • put the amount to 'to'
  • (ideally, that should be a single 'business' transaction).

It is obvious that this function doesn't fit Account entity - so it would be nice to have a special TransferService for that.


Specifically about Report/ReportGroup functionality - it could be Entity scope, as there are no non-logical references (all items are reachable from report):

Report.Remove() {
    foreach(group in Groups) {
        group.RemoveReport(this);
    }
    repository.Remove(this);
} 
Group.RemoveReport(report) {
    reports.Remove(report);
    if(reports.Count == 0)
        repository.Remove(this);
}

or split between a report service and entities (referencing repositories could be problematic):

Report.RemoveFromAllGroups() {
   foreach(group in Groups) {
        group.RemoveReport(this);
        if(group.IsEmpty)
            //add to collection to return
    }
}
Service.Remove(report) 
{
    var emptyGroups = report.RemoveFromAllGroups();
    reportRepo.Remove(report);
    groupRepo.Remove(emptyGroups);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top