(Cross post from IRefactor)
When designing an application, one can easily confuse DTOs, Business Entities and Persistency.
Using the following simple examples, I will demonstrate design considerations and thoughts that will dispel some myths.
Consider that you want to represent an audio or a movie located on a web page.
When you visualize such an object, the first thing you probably see is the data that characterizes it. No surprise here. You applied, without knowing it, an "Information Expert" pattern when you visualized the object's knowledge responsibilities.
So, what are they? Clearly such an object will have:
- URI - object's URL
- Content - object's content
- DateCreated - object's creation date
- Etc.
I will name this object a "
Resource
", since it represents a resource being located on a web page (be it a movie or an audio).
But wait... here you go; Meet the DTO object. A DTO is a simple container for a set of aggregated data that needs to be transferred across a process or network boundary. It should contain no business logic and limit its behavior to activities such as internal consistency checking and basic validation.
The Resource
object is rather simple, it's a mere placeholder for the object's attributes and as such represents a DTO.
Now, let's say that a Resource
object should be persisted to some store.
Basically, we would like to introduce a "Save" functionality that will persist a Resource
object. The question is: Who's responsibility should it be? Using the Information Expert pattern again will reveal that functional responsibility (as a "Save" operation) is the sole responsibility of a class with the most information required to fulfill it. Hmmm... Isn't it a Resource
class? As a Resource
class "knows" exactly the information it wants to persist, it implies that the Resource
class should have the "Save" responsibility.
I know... I know... didn't we say a minute ago that a DTO shouldn't contain any business logic? By adding a Save responsibility to the resource class, we turned it into something else. We turned the Resource into Business Entity. A business entity is an object that is responsible to solve domain requirements by collaborating with additional business entities.
Let's see what are our design options for creating a Resource business entity.
Option I: A Resource business entity inherits a ResourceDTO
.
Though it's tempting to select the above solution, you shouldn't take the path (unless the requirements are very very simple). Remember: One of the principles of object oriented design is to favor object composition over class inheritance. There are many reasons for which the principle holds, but I am not going to discuss all of them here.
It does not make sense that Resource "is-a" type of ResourceDTO
; You are not going to transfer the Resource
object across boundaries. Moreover, such a design may violate the Single Responsibility and Open Closed Principles. The "Save
" method is going to be bloated with a specific logic of how to connect to a certain store - which is clearly not Resource's responsibility. Changing a store, having more than one store or changing what to store will require Resource modifications - which violates the OC principle.
Option II: A Resource business entity passes a Resource DTO to a specialized Resource data access entity.
All the specific logic of data access is captured in a specialized ResourceDal
object. Any change in store will change only the specialized object. The ResourceDal
object will receive a Persistent Resource DTO object, thus decoupling the Resource entity from the persistent information (which can vary easily later on). Such a design is superior as it allows to inject the correct data access object to the Resource entity (and it also means that it will be much more testable). Also, it allows to have multiple DTOs, for example: one for persistency and one for visualization.
In order to derive a PersistentResourceDTO
from a Resource entity, I will use an AutoMapper library.
AutoMapper
uses a fluent configuration API to define an object to object mapping strategy, thus allowing easy transformations.
Here is the PersistentResourceDTO
declaration:
[Serializable]
public class PersistedResourceDTO
{
public Uri Uri { get; private set; }
public byte[] Content { get; private set; }
public DateTime DateCreated { get; private set; }
}
Using AutoMapper
library is especially easy when the properties and methods of the source type match the properties of the destination type - this is called flattening.
Thus ExtractPersistentDTO
method's implementation is simple and coherent.
private PersistedResourceDTO ExtractPersistentDTO()
{
Mapper.CreateMap<Resource, PersistedResourceDTO>();
PersistedResourceDTO dto = Mapper.Map<Resource, PersistedResourceDTO>(this);
return dto;
}
AutoMapper
library also allows to project a mapping. Projection - transforms a source to a destination beyond flattening the object model. Consider that a Resource also has a collection of keywords. In order to visualize the Resource, we want to use the following VisualResourceDTO
where the title will be a combination of all the keywords.
[Serializable]
public class VisualResourceDTO
{
public Uri Uri { get; private set; }
public string Title { get; private set; }
}
Here is how to do it using the AutoMapper
:
private VisualResourceDTO ExtractVisualDTO()
{
Mapper.CreateMap<Resource, VisualResourceDTO>()
.ForMember( d => d.Title,
op => op.MapFrom(resource =>
string.Join(" ", resource.Keywords.ToArray()))
);
VisualResourceDTO dto = Mapper.Map<Resource, VisualResourceDTO>(this);
return dto;
}
(Remark: In the post above, I didn't discuss other approaches like ORM solutions).
CodeProject