I'm currently refactoring a ASP.NET MVC project to use the onion arcitecture since it seems that it suits the needs of future development.
I have set up the layers which I think I need to use and my solution now looks like this:
So, basically as I've understood, the ClientName.Core
project should not have any references to other projects at all. The ClientName.Infrastructure
should have a reference to the ClientName.Core
. The Interfaces folder on ClientName.Core
defines the services which is in the ClientName.Infrastructure
project and my DbContext and domain entities are separated so only the entities are in the core project.
Where I ran my head against the wall was, that the ClientName.Infrastructure
project should not return domain entities to what-ever client that is calling it. This would create a reference between the core project and any UI which "violates" the onion principle. A way around this, as I've read, is to make the infrastructure services return DTO´s instead. However, if I'm returning i.e. a PersonDto
from the PersonService
class, the PersonDto
object needs to be known by the ClientName.Core
project since that is where the interface is.
So the question is: where exactly do I place the DTO/ViewModel/other models used for the UI/client? Do I create a separate class library which holds these models and let both the UI, infrastructure and core projects reference it?
Any help/hint is greatly appreciated as I'm a bit confused about this ;-)
Thanks in advance.
EDIT
Based on Euphorics answer, I'm writing up example code here just to check if I got it right and maybe with some follow-up questions.
So basically, in my ClientName.Core
layer, I have my entities which contains business logic, i.e. a Person
and a Firm
entity could look like this:
(Exists in ClientName.Core/Entities)
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Firm Firm { get; set; }
}
(Exists in ClientName.Core/Entities)
Public class Firm
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Person> Employees { get; set; }
public IEnumerable<Person> GetEmployeesByName(string name)
{
return Employees.Where(x => x.Name.Equals(name));
}
public void AddEmployee(Person employee)
{
Employees.Add(employee);
}
public void CreateFirm(Firm firm)
{
// what should happen here? Using entity framework, I have no reference to the DbContext here...
}
}
(Exists in ClientName.Infrastructure/Services)
public class FirmService : IFirmService
{
private readonly IDbContext _context;
public FirmService(IDbContext context)
{
_context = context;
}
public Firm GetById(int firmId)
{
return _context.Firms.Find(firmId);
}
// So basically, this should not call any domain business logic?
public void CreateFirm(CreateFirmFormViewModel formViewModel)
{
Firm firm = new Firm()
{
Name = formViewModel.Name;
}
_context.Firms.Add(firm);
_context.SaveChanges();
}
public IEnumerable<Person> GetEmployeesByName(int firmId, string name)
{
Firm firm = _context.Firms.Find(firmId);
return firm.GetEmployeesByName(name);
}
}
Am I correct that every read-query should be defined directly on the entity (maybe as a entity extension since I'm using Entity Framework) and any create/update/delete would happen only in the services in the infrastructure layer?
Should the read (i.e. the IEnumerable<Person> GetEmployeesByName(int firmId, string name)
method) methods also be on the FirmService
interface/class?
Thanks again :-)
DTOs are inside Domain Layer... I think it really depends on where you intend to use those DTOs. For example, if the DTOs will mainly used by the frontend that relies on some private APIs then I would put the DTOs into the UI layer as other parts of the system do not need to know about those DTOs.
DTOs are Data Transfer Objects. They should be used when there is a network call involved because they are lightweight. Entities can be heavy and contain domain logic which may not be necessary to be transmitted over a network. DTOs are used to only pass data without exposing your domain entities.
Infrastructure Layer – this is the outermost layer of onion architecture which deals with Infrastructure needs and provides the implementation of your repositories interfaces. In other words, this is where we hook up the Data access logic or logging logic or service calls logic.
First, you are not returning Person
from PersonService
, but from IPersonService
. This is huge conceptual difference. PersonService only implements what IPersonService
defines. It doesn't care what interfaces or objects it uses.
In short, the Core project should include all the business logic in business entities. Which if you don't have any actual logic, would be just plain DTOs. Then, the Infrastructure
would be responsible for saving/loading those entities from database. If it creates it's own model with it's own entities or if it reuses the ones defined by core is Infrastructure
's implementation detail. At the same time, the UI
(or Web) project will work with the entities in the Core
project, but it won't care how the persistence happens. It just wants to "do the business".
The key point here, is that the Core (or the business logic) is auto-testable without involving UI
or database code. As long as you are able to achieve that, it doesn't matter where some DTOs are.
Edit:
This is starting to get hard. You are experiencing conflicting design requirements. On one side, you have clear domain design. On the other, you want your model to be persistable using EF. One thing is clear : you are going to compromise and twist your domain model so it can be used in EF.
Now for concrete issues. First one is creation of the firm. Here, you have conflict between "new Firm" as understood by domain. Eg. making sure new Firm has valid name. On the other, you want to create the instance and make EF aware of it. The way I would do it is to remove the CreateFirm
from Firm
class and change CreateFirm
in IFirmService
so that it accepts only the parameters necessary to create new Firm
(eg. just the name) and return the newly created firm. Also, the idea that repository shouldn't call the domain is wrong. For example, the repository might call the domain to validate if the Firm it created is valid and if not, then don't add it in EF.
Second issue I see is keeping of Employees collection in Firm. While this is great for domain, it is disaster for persistence (unless you think lazy loading is fine in this scenario). Again. There is no simple way how to satisfy the domain requirement of "Firm has collection of Employees" and ability to persist the entity into database. First option is not to have this collection and move it to the IFirmService
, where it can be properly implemented. Third option is most extreme. And that is to make Firm
an abstract class while making all the "get"s an abstract methods, create an implementation of it in the infrastructure project and implement the get methods using the EF context the concrete instance would have. Also, this could be possible only if concrete instance of Firm
is created in the infrastructure, which is what I suggested above.
I personally like the third option, because it keeps the methods nice and cohesive. But some EF purists might disagree with me.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With