Oren posted a pattern for converting Domain entities to DTO’s using a delegate. He made a comment in his post: “I did try to make it into a framework, but even I can’t make it more complex than this.”
I started laughing to myself because I actually tried something similar a few weeks ago, and was going to post, but I didn’t cuz I’m fairly new to this blog stuff and I wussed out… Time to be bold I suppose.
The project I am working on now has a large Service Layer on top of Repositories. You can only write so many services with translation before you start to see the commonalities and look for a way to abstract and reuse. (framework)
So, I thought I’d geek out and get carried away with some Fowler P of EEA. Basically, what I wanted to do is *try* and put some standardization to a Service Layer, which returns DTO’s, which are assembled from a Domain Model, which are accessed from Repository. So DTO <-> Service Layer <-> Assembler <-> Repository <-> Domain Model. Sweet. I am going to use the classic Fowler Album and Artist.
Abstractions
The Repository interface:
public interface IRepository<TDomainEntity>
{
IEnumerable<TDomainEntity> Find(Func<TDomainEntity, bool> filter);
TDomainEntity GetById(long Id);
void Add(TDomainEntity domainEntity);
void Delete(TDomainEntity domainEntity);
void Update(TDomainEntity domainEntity);
}
The abstract Assembler:
public abstract class Assembler<TDto, TDomainEntity>
{
public abstract TDomainEntity DtoToDomainEntity(TDto dto);
public abstract TDto DomainEntityToDto(TDomainEntity domainEntity);
public List<TDto> DomainEntitiesToDtos(IEnumerable<TDomainEntity> domainEntityList)
{
List<TDto> dtos = Activator.CreateInstance<List<TDto>>();
foreach (TDomainEntity domainEntity in domainEntityList)
{
dtos.Add(DomainEntityToDto(domainEntity));
}
return dtos;
}
public List<TDomainEntity> DtosToDomainEntities(IEnumerable<TDto> dtoList)
{
List<TDomainEntity> domainEntities = Activator.CreateInstance<List<TDomainEntity>>();
foreach (TDto dto in dtoList)
{
domainEntities.Add(DtoToDomainEntity(dto));
}
return domainEntities;
}
}
And a base Service:
public class Service<TDto, TDomainEntity>
{
Assembler<TDto, TDomainEntity> _assembler;
IRepository<TDomainEntity> _repository;
public Service(Assembler<TDto, TDomainEntity> assembler,
IRepository<TDomainEntity> repository)
{
_assembler = assembler;
_repository = repository;
}
public Assembler<TDto, TDomainEntity> Assembler { get { return _assembler; } }
public IRepository<TDomainEntity> Repository { get { return _repository; } }
public virtual List<TDto> Find(Func<TDomainEntity, bool> filter)
{
return _assembler.DomainEntitiesToDtos(_repository.Find(filter));
}
public virtual TDto GetById(long Id)
{
return _assembler.DomainEntityToDto(_repository.GetById(Id));
}
public virtual void Add(TDto dto)
{
_repository.Add(_assembler.DtoToDomainEntity(dto));
}
public virtual void Update(TDto dto)
{
_repository.Update(_assembler.DtoToDomainEntity(dto));
}
public virtual void Delete(TDto dto)
{
_repository.Delete(_assembler.DtoToDomainEntity(dto));
}
}
Implementations
Now that I have some structure down, I think hammering out the implementations should go fast. I have more or less templated how the objects should interact.
The Domain Entities:
public class Artist
{
public string Name { get; set; }
}
public class Album
{
public long Id { get; set; }
public string Title { get; set; }
public Artist Artist { get; set; }
}
The DTO:
public class AlbumDto
{
public long Id { get; set; }
public string ArtistName { get; set; }
public string Title { get; set; }
}
The Repository: (With some hacked data in a List)
public class AlbumRepository : IRepository<Album>
{
#region IRepository<Album> Members
List<Album> _albums = new List<Album>
{
new Album {Id=1, Title="Rock Out", Artist=new Artist{Name="Rock Dudes"}},
new Album {Id=2, Title="Rock On", Artist=new Artist{Name="Rock Dudes"}},
new Album {Id=3, Title="Yee haw", Artist=new Artist{Name="The Hicks"}}
};
public IEnumerable<Album> Find(Func<Album, bool> filter)
{
return _albums.Where(filter);
}
public Album GetById(long Id)
{
return _albums.Find(a => a.Id == Id);
}
public void Add(Album domainEntity)
{
_albums.Add(domainEntity);
}
public void Delete(Album domainEntity)
{
_albums.Remove(domainEntity);
}
public void Update(Album domainEntity)
{
Album album = _albums.Find(a => a.Id == domainEntity.Id);
_albums.Remove(album);
_albums.Add(domainEntity);
}
#endregion
}
public class AlbumAssembler : Assembler<AlbumDto, Album>
{
public override AlbumDto DomainEntityToDto(Album domainEntity)
{
return new AlbumDto
{
Id = domainEntity.Id,
ArtistName = domainEntity.Artist.Name,
Title = domainEntity.Title
};
}
public override Album DtoToDomainEntity(AlbumDto dto)
{
Album album = new Album { Id = dto.Id, Title = dto.Title };
album.Artist = new Artist { Name = dto.ArtistName };
return album;
}
}
And of course the Service: (Super easy now)
public class AlbumService : Service<AlbumDto, Album>
{
public AlbumService()
: base(new AlbumAssembler(), new AlbumRepository()) { }
public AlbumDto FindByAlbumTitle(string title)
{
return Find(a => a.Title == title).First<AlbumDto>();
}
}
I went ahead and added another method using Find, but its nice to see all the standard interactions already there. If I need to extend the Service I can as all the methods defined are virtual and the Repository and Assembler are exposed as properties:
So there it is. I am not sure how I quite feel about it yet. I have a side project going and I think Ill try it out and see how it goes. It was certainly fun to do, and I’m definitely interested in your thoughts.
Though I posted all the code, you can find it here if your interested.
How about in your abstract assembler doing the following?
public IEnumerable DomainEntitiesToDtos(IEnumerable domainEntityList)
{
foreach (TDomainEntity domainEntity in domainEntityList)
{
yield return DomainEntityToDto(domainEntity);
}
}
public IEnumerable DtosToDomainEntities(IEnumerable dtoList)
{
foreach (TDto dto in dtoList)
{
yield return DtoToDomainEntity(dto);
}
}
I dig it. Much cleaner. I was initially thinking I would want list functionality returned from the assembler to the service, but that is really not a concern of the assembler. Could always do a new List in the service if ever needed (actually cant think of a good reason), defer it to the latest possible moment.
Thanks for the suggestion!
Good stuff.
Thank you for sharing.