Bradley Braithwaite
1 import {
2 Learning,
3 JavaScript,
4 AngularJS
5 } from 'bradoncode.com'
6 |

Repository vs. Domain Model vs. Data Mapper

on
on Design Patterns, asp.net 10 years on

This post looks at creating a data access layer where the domain model is very different from the relational design of the database. This post is part of a blog series ASP.Net 10 Years On. Even though this is part of a series each post can be read standalone.

A few posts back, the data access implementation for the SMS application was revisited and replaced with an ORM; Entity Framework to be precise. Since we had made use of the repository pattern in the API layer it meant that the data access implementation could be replaced without making any changes in the other layers, which is definitely a good thing about the architecture.

But, if we look at the ORM implementation using Entity Framework it most definitely looks messy. Why? Because the domain model is very different from the database table structure.

Consider the following Domain Model class structure:

Domain model

Now contrast the relational database structure:

Db model

The full domain model objects are not included for brevity, but it should convey the problem.

Square Peg, Round Hole

What’s happening is the domain model is meeting the database structure head on, almost like trying to make a square peg fit in a round hole. This problem is called Object-relational impedance mismatch.

// POST users
public HttpResponseMessage Post(PostUser item)
{
    var user = new User()
        {
            Username = item.Username,
            Password = EncryptedString.Create(item.Password, _encryptionService)
        };
 
    if (user.IsValid)
    {
        _userRepository.Add(user);
        _unitOfWork.SaveChanges();
 
        GetUser createdItem = _mapper.Map<User, GetUser>(user);
        return CreatedHttpResponse(createdItem.ID, createdItem);
    }
 
    return Request.CreateResponse(HttpStatusCode.BadRequest, user.ValidationErrors);
}

Buried within the repository is the kludge that maps between domain model and database structure mixed in with the Unit of Work context from EF. It’s not pretty but is unavoidable given the structure of the two objects.

The following code is snippet is the Add method from the UserRepository. The call to UserMapper on line 10 is the muddy bit:

/// <summary>
/// Adds a new user.
/// </summary>
/// <param name="item">
/// The item.
/// </param>
public void Add(User item)
{
    item.ID = Guid.NewGuid();
    _context.Entry(UserMapper.MapTo(item)).State = EntityState.Added;
}

NB regular Entity Framework Code First users will know that it is possible to use the Fluent API for mapping between the domain and database structure. The use of factory classes, value objects etc within the domain model pushed the boundaries of what’s possible (and sensible) using the Fluent API.

Next we look at an example of the code that would map from the User domain entity to the UserData class. The UserData class represents the structure of the database table. We would need to write this sort of code from both to and from database interactions:

/// <summary>
/// Maps from the User domain entity to UserData EF entity.
/// </summary>
/// <param name="item">
/// The item.
/// </param>
/// <returns>
/// The <see cref="UserData"/>.
/// </returns>
internal static UserData MapTo(User item)
{
    if (item == null)
    {
        return null;
    }
 
    var data = new UserData
        {
            ID = item.ID,
            Password = item.Password.EncryptedValue,
            Username = item.Username
        };
 
    return data;
}

This code doesn't look too offensive but it results in more work. It’s an extra layer of kludge muddying the Entity Framework implementation. Most definitely a code smell.

Data Mapper

An alternative and probably more ideal approach is the data mapper pattern defined in Martin Fowler's EAA Catalog:

The Data Mapper is a layer of software that separates the in-memory objects from the database. Its responsibility is to transfer data between the two and also to isolate them from each other. With Data Mapper the in-memory objects needn't know even that there's a database present; they need no SQL interface code, and certainly no knowledge of the database schema.

Compare this with a description of the Repository pattern:

A system with a complex domain model often benefits from a layer, such as the one provided by Data Mapper (165), that isolates domain objects from details of the database access code. In such systems it can be worthwhile to build another layer of abstraction over the mapping layer where query construction code is concentrated. This becomes more important when there are a large number of domain classes or heavy querying. In these cases particularly, adding this layer helps minimize duplicate query logic.

The MSDN article titled Persistence Patterns speaks of the Data Mapper pattern:

Data Mapper is more appropriate for systems with complex domain logic where the shape of the domain model will diverge considerably from the database model. Data Mapper also decouples your domain model classes from the persistence store. That might be important for cases where you need to reuse the domain model with different database engines, schemas, or even different storage mechanisms altogether.

In terms of isolation and testability, the Data Mapper pattern is similar to the Repository pattern. So, what's the key difference between Data Mapper and Repository?

The repository builds on the foundations of the Data Mapper pattern but adds an "additional layer of abstraction over the mapping layer where query construction code is concentrated" in Fowler speak. I would describe this extra layer as a collection-like interface for querying domain objects via a querying format such as Linq.

It is possible to implement the Repository not using an ORM such as Entity Framework, but we would be in danger of re-inventing the ORM wheel (again) in creating this extra layer of abstraction/collection-like interface for querying. The trade off in this application, is we loose the querying abstraction and unit of work context, but we gain a less complex abstraction that leaks less i.e. we don't have to write the additional mapping layer to wrap Entity Framework interactions between the repository and the database.

The Data Mapper Implementation

The starting point to the implementation are the following interface contracts:

image

The Web API that used to make use of the Repository/Unit of Work patterns will be changed from the current structure:

image

When changed, the user controller now uses a UserDataMapper instance:

image

Revisiting the API POST method in the UsersController, it has been changed to use the new UserDataMapper interface:

// POST users
public HttpResponseMessage Post(PostUser item)
{
    var user = new User()
        {
            Username = item.Username,
            Password = EncryptedString.Create(item.Password, _encryptionService)
        };
 
    if (user.IsValid)
    {
        /// OLD
        //_userRepository.Add(user);
        //_unitOfWork.SaveChanges();
 
        // NEW
        _userDataMapper.Insert(user);
 
        GetUser createdItem = _mapper.Map<User, GetUser>(user);
        return CreatedHttpResponse(createdItem.ID, createdItem);
    }
 
    return Request.CreateResponse(HttpStatusCode.BadRequest, user.ValidationErrors);
}

The concrete implementation for the UserDataMapper looks as follows:

/// <summary>
/// The user mapper.
/// </summary>
public class UserDataMapper : IUserDataMapper
{
    /// <summary>
    /// Gets the connection.
    /// </summary>
    internal IDbConnection Connection
    {
        get { return new SqlConnection(ConfigurationManager.ConnectionStrings["SmsQuizConnection"].ConnectionString); }
    }
 
    /// <summary>
    /// Find by username.
    /// </summary>
    /// <param name="username">
    /// The username.
    /// </param>
    /// <returns>
    /// The <see cref="User"/>.
    /// </returns>
    public User FindByUsername(string username)
    {
        return FindSingle("SELECT * FROM Users WHERE Username=@Username", new { Username = username });
    }
 
    /// <summary>
    /// Find by id.
    /// </summary>
    /// <param name="id">
    /// The id.
    /// </param>
    /// <returns>
    /// The <see cref="User"/>.
    /// </returns>
    public User FindById(Guid id)
    {
        return FindSingle("SELECT * FROM Users WHERE ID=@ID", new { ID = id });
    }
 
    /// <summary>
    /// Find all.
    /// </summary>
    /// <returns>
    /// The <see cref="IEnumerable"/>.
    /// </returns>
    public IEnumerable<User> FindAll()
    {
        var items = new List<User>();
 
        using (IDbConnection cn = Connection)
        {
            cn.Open();
            var results = cn.Query("SELECT * FROM Users").ToList();
 
            for (int i = 0; i < results.Count(); i++)
            {
                items.Add(MapUser(results.ElementAt(i)));
            }
        }
 
        return items;
    }
 
    /// <summary>
    /// Insert user.
    /// </summary>
    /// <param name="item">
    /// The item.
    /// </param>
    public void Insert(User item)
    {
        using (IDbConnection cn = Connection)
        {
            cn.Open();
            item.ID =
                cn.Query<Guid>(
                    "INSERT INTO Users (Username, Password) OUTPUT inserted.ID VALUES (@Username, @Password)",
                    new { item.Username, Password = item.Password.EncryptedValue }).First();
        }
    }
 
    /// <summary>
    /// Update user.
    /// </summary>
    /// <param name="item">
    /// The item.
    /// </param>
    public void Update(User item)
    {
        using (IDbConnection cn = Connection)
        {
            cn.Open();
            cn.Execute(
                "UPDATE Users SET UserName=@UserName, Password=@Password WHERE ID=@ID",
                new { item.Username, Password = item.Password.EncryptedValue });
        }
    }
 
    /// <summary>
    /// The delete.
    /// </summary>
    /// <param name="id">
    /// The id.
    /// </param>
    public void Delete(Guid id)
    {
        using (IDbConnection cn = Connection)
        {
            cn.Open();
            cn.Execute("DELETE FROM Users WHERE ID=@ID", new {ID = id});
        }
    }
 
    /// <summary>
    /// The map user.
    /// </summary>
    /// <param name="result">
    /// The result.
    /// </param>
    /// <returns>
    /// The <see cref="User"/>.
    /// </returns>
    private User MapUser(dynamic result)
    {
        var item = new User
            {
                ID = result.ID,
                Username = result.Username,
                Password = new EncryptedString(result.Password)
            };
 
        return item;
    }
 
    /// <summary>
    /// Find a single record.
    /// </summary>
    /// <param name="query">
    /// The query.
    /// </param>
    /// <param name="param">
    /// The parameter values required by the query.
    /// </param>
    /// <returns>
    /// The <see cref="User"/>.
    /// </returns>
    private User FindSingle(string query, dynamic param)
    {
        Guid id;
        User item = null;
 
        using (IDbConnection cn = Connection)
        {
            cn.Open();
            var result = cn.Query(query, (object)param).SingleOrDefault();
 
            if (result != null)
            {
                item = MapUser(result);
            }
        }
 
        return item;
    }
}

There is scope to abstract away some common elements for re-use across multiple mapper implementations. You may notice that this code uses Dapper.net for data access which is similar to the first repository implementation using Dapper, that was replaced.

The key points to this post:

  • The repository is not the answer to all data access problems.
  • If you find yourself writing a lot of mapping code but are using the Repository pattern, consider the data mapper pattern.
  • Sometimes we are guilty of not keeping things simple.

</div>

SHARE
Don't miss out on the free technical content:

Subscribe to Updates

CONNECT WITH BRADLEY

Bradley Braithwaite Software Blog Bradley Braithwaite is a software engineer who works for the search engine duckduckgo.com. He is a published author at pluralsight.com. He writes about software development practices, JavaScript, AngularJS and Node.js via his website . Find out more about Brad. Find him via:
You might also like:
mean stack tutorial AngularJS Testing - Unit Testing Tutorials