This is a follow-up for my question from here.
First of all, I am not using DDD in my project.
I have a WCF service with 3 layers:
- The service layer (only holds operations and calls BL methods)
- The business logic layer that holds all the business logic classes and methods
- The data access layer that hold the DbContext (LINQ-TO-EF) and POCO entities
The WCF service needs to return DTO objects, and I am having trouble figuring out the best place to put the 'Translator' class that translates my POCO entities to DTOs.
I had 2 options for this:
.
.
METHOD A
Have the business logic methods return entities to the service layer, and the service layer has a translator class in it that translates entities to DTOs.
PROS:
- Business logic layer does what it has to do - validation and CRUD operations
- Business logic layer does not need to know about DTOs at all
CONS:
- Service layer now has to include a reference to the 'data access layer' assembly, because it receives entities from the business logic layer. This seems to break the 3-tier concept, which says that the service layer needs to reference the BL layer only, and the BL layer needs to reference the DAL only.
- This is the worst problem : the translator class needs to create DTOs from entity objects. Because it receives the entity objects from the BL after the DbContext was disposed of, it cannot access anything that was not loaded with the 'Include' extension. This means that the BL methods needs to return entities back to the service layer, with everything that would be needed by the translator in order to create DTOs. This is a problem because it requires the BL to know what the translator needs, and second - it will fetch a lot of unnecessary data from the database ! (maybe the translator needs to return a 'UserDto' object that one of it's fields is 'total number of orders' - why would I want to fetch all the orders from the database just to make a 'Count()' call in the translator ?
.
.
METHOD B
Have the 'Translator' class that translates from entity objects to DTOs placed in the 'business logic layer' itself. In this mechanism - the BL methods return DTOs already.
PROS:
- The BL method perform the BL code, and then calls the 'translate_to_dto' appropriate message to translate the results to a DTO that is returned. This is all done inside the 'DbContext' which means that when calling the translator class to translate an entity, it can still access the child objects, and no call to 'Include' is needed. This means that only the data that is needed to create the DTO is fetched from the database.
CONS:
- Now the 'translator' class that is in the 'business logic layer' is the one who needs to know about DTOs, even though it is the responsibility of the service layer to know about them only !
- Each method in the BL now perform pure BL (validity checks, CRUD operations etc), and in addition calls the translator method in order to return a DTO. This break the 'Single Responsibility Rule' that says that a method (in the BL) should only do one specific thing.
.
Can anyone tell me where the right place is to perform the 'Entity ==> DTO' conversion ?
.
[Update - Added Example]
Business Logic layer has a manager class called UserManager that has a BL method like this:
public UserTasksDto GetUserInfoWithTasks(Guid userId)
{
if (userId == Guid.Empty)
throw new ArgumentException("User ID cannot be empty");
using (IMyDBEntities entities = _contextFactory.GetContext())
{
// Get POCO Object from DbContext
User user = entities.Users.Find(userId);
if (user == null)
throw new EntityNotFoundException("User was not found in the database");
if (user.Tasks.Count() == 0)
throw new Exception("User does not have any tasks !");
// Call 'Translator' static method to translate POCO to DTO
Translator.TranslateUserToUserTasksDto(user);
}
}
As you can see above - the BL method calls a 'translator' method to turn POCO to DTO. This is done inside the 'entities' context, so that the translator can still access the user's 'Tasks' children.
Here is what the 'Translator' method looks like:
class Translator
{
public static UserTasksDto TranslateUserToUserTasksDto ( User userPoco )
{
UserTasksDto dto = new UserTasksDto
{
UserId = userPoco.Id,
Username = userPoco.Username,
CreationDate = userPoco.CreationDate,
// Accessing a related entity, this is why this 'translate' method
// needs to be called inside the DbContext, otherwise it will except
// (or we load all related entities using 'Include' just for the 'Count' purpose)
Supervisor = userPoco.Supervisor.Username,
NumOfTasks = userPoco.Tasks.Count(),
FirstTaskDate = userPoco.Tasks.OrderBy(task => task.Date).Take(1),
}
return dto;
}
}
As you can see above - the 'Translate' method 'builds' a 'UserTasksDto' from a 'User' POCO object. This is done by mapping some fields from the 'User' object and it's related entities to the DTO. If this method was not inside the BL method's ObjectContext - I would get an exception saying I am trying to access entities without a context.
I hope my problem now is more clear...