Data transfer objects are very useful for preparing domain information for a presentation layer, and this usually means the UI. But they are at least as useful when it comes to transferring information between different environments, especially when serialization is involved.
Here is an example: I needed to serialize a hierarchy of value objects in JSON, so I can put them in a hidden field and use them in an algorithm on the client-side.
These are the value objects:
public interface ICombinationOfProfiles
{
IList<ProfileInCombinationWithOtherProfiles> ProfilesInCombinationWithOtherProfiles { get; }
void AddProfileInCombinationWithOtherProfiles(ProfileInCombinationWithOtherProfiles combination);
bool IsEmpty { get; }
}
public class CombinationOfProfiles : ICombinationOfProfiles
{
private IList<ProfileInCombinationWithOtherProfiles> _profilesInCombinationWithOtherProfiles = new List<ProfileInCombinationWithOtherProfiles>();
public IList<ProfileInCombinationWithOtherProfiles> ProfilesInCombinationWithOtherProfiles
{
get { return new ReadOnlyCollection<ProfileInCombinationWithOtherProfiles>(_profilesInCombinationWithOtherProfiles); }
set { _profilesInCombinationWithOtherProfiles = value; }
}
public void AddProfileInCombinationWithOtherProfiles(ProfileInCombinationWithOtherProfiles combination)
{
_profilesInCombinationWithOtherProfiles.Add(combination);
}
public bool IsEmpty
{
get { return _profilesInCombinationWithOtherProfiles.Count == 0; }
}
}
- ProfileInCombinationWithOtherProfiles:
public class ProfileInCombinationWithOtherProfiles
{
private readonly Product _profile;
private readonly ProductCombinationForProfile _combinationOfProductsForProfileWhichIsValidInCombination;
public ProfileInCombinationWithOtherProfiles(Product profile, ProductCombinationForProfile combinationOfProductsValidInCombinationForProfile)
{
_profile = profile;
_combinationOfProductsForProfileWhichIsValidInCombination = combinationOfProductsValidInCombinationForProfile;
}
public ProductCombinationForProfile ValidCombinationOfProducts
{
get { return _combinationOfProductsForProfileWhichIsValidInCombination; }
}
public Product Profile
{
get { return _profile; }
}
}
- ProductCombinationForProfile:
public class ProductCombinationForProfile
{
private readonly IList<Product> _products = new List<Product>();
public IList<Product> Products
{
get { return new ReadOnlyCollection<Product>(_products); }
}
public void AddProductInCombination(Product p)
{
_products.Add(p);
}
}
As you can see, they encapsulate a list of products (profiles) and for each product a list (valid combination) of products.
I initially tried to serialize the list of CombinationOfProfiles:
hdnProfileAssistanceData.Value =
JsonConverter<CombinationOfProfiles>.Serialize(dto.PossibleCombinations);
(dto.PossibleCombinations is an IList<CombinationOfProfiles>)
This got me an error, because the hierarchy contains Product entities (which contain subentities and a whole business structure), and these are not serializable.
So I have 2 options: either make the Product entity serializable, or find another way to transmit the information I need to the client.
Making the Product entity serializable is a bad idea, because it would mean I am doing changes in my domain classes because of an infrastructure issue. This will impose initial restrictions, and in time it will become a problem. The domain must remain independent and uninterested of the other layers.
I realized I only needed the Ids of the products and nothing else, because on the client-side I just wanted to check al valid combinations based on this hierarchy. And this made things very simple.
I created a set of DTOs for JSON serialization:
[Serializable]
public class CombinationOfProfilesJSONDTO
{
public CombinationOfProfilesJSONDTO()
{
}
public CombinationOfProfilesJSONDTO(IEnumerable<ProfileInCombinationWithOtherProfiles> profiles)
{
Profiles = new List<ProfileInCombinationWithOtherProfilesJSONDTO>();
foreach(ProfileInCombinationWithOtherProfiles profile in profiles)
{
Profiles.Add(new ProfileInCombinationWithOtherProfilesJSONDTO(profile));
}
}
public IList<ProfileInCombinationWithOtherProfilesJSONDTO> Profiles { get; set; }
}
[Serializable]
public class ProfileInCombinationWithOtherProfilesJSONDTO
{
public ProfileInCombinationWithOtherProfilesJSONDTO(ProfileInCombinationWithOtherProfiles profile)
{
Product = profile.Profile.Id;
ProductCombinationForProfile = new ProductCombinationForProfileJSONDTO(profile.ValidCombinationOfProducts);
}
public int Product { get; set; }
public ProductCombinationForProfileJSONDTO ProductCombinationForProfile { get; set; }
}
[Serializable]
public class ProductCombinationForProfileJSONDTO
{
public ProductCombinationForProfileJSONDTO(ProductCombinationForProfile products)
{
Products = new List<int>();
foreach(Product p in products.Products)
Products.Add(p.Id);
}
public IList<int> Products { get; set; }
}
and a mapper between a CombinationOfProfiles domain value object and an CombinationOfProfilesJSONDTO:
public class CombinationOfProfilesJSONMapper : IMapper<CombinationOfProfiles, CombinationOfProfilesJSONDTO>
{
public void CopyFrom(CombinationOfProfiles source, CombinationOfProfilesJSONDTO destination)
{
throw new System.NotImplementedException();
}
public CombinationOfProfilesJSONDTO MapFrom(CombinationOfProfiles input)
{
return new CombinationOfProfilesJSONDTO(input.ProfilesInCombinationWithOtherProfiles);
}
}
Here is how my call looks now, which generates the entire hierarchy of DTOs automatically:
hdnProfileAssistanceData.Value =
JsonConverter<CombinationOfProfilesJSONDTO>.Serialize(
dto.PossibleCombinations.MapAllUsing(new CombinationOfProfilesJSONMapper()));