0

I have a domain object, Address that may be populated from a variety of data sources, which requires a lot of mapping code. In the interest of "Closed to Modification" I want to be able to create individual "Mappers" for each data source. I can then pass the mapper into an instance of the Address and VOILA! get an appropriate data entity back in response. And vice-versa, I also want implement a method on that Address that will allow me to map an entity into a new or to populate an existing instance of the Address.

I create my Address object ...

public class Address
{
    public string Street1 { get; set; }
    public string Street2 { get; set; }
    public string Street3 { get; set; }
    public string Street4 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Country { get; set; }
    public string PostalCode { get; set; }
}

Now I create a couple of classes that will facilitate mapping specific data entity objects to and from this Address object.

//
// Maps to and from a Database object (DB1_ADDRESS)
//
public class DB1AddressMapper
{
    property DB1_ADDRESS _entity;

    public DB1AddressMapper()
    {

    } 

    public DB1AddressMapper(DB1_ADDRESS entity)
    {
        _entity = entity;
    }

    public DB1_ADDRESS MapModelToEntity(Address model)
    {
        DB1_ADDRESS ret = new DB1_ADDRESS();

        <... mapping logic goes here>

        return ret;
    }

    public Address MapEntityToModel()
    {
        Address ret = new Address();

        <... mapping logic goes here>

        return ret;
    }
}

//
// Maps to and from a WebService response (WS_ADDRESS)
//
public class WSAddressMapper
{
    property WS_ADDRESS _entity;

    public WSAddressMapper()
    {

    } 

    public WSAddressMapper(WS_ADDRESS entity)
    {
        _entity = entity;
    }

    public WS_ADDRESS MapModelToEntity(Address model)
    {
        WS_ADDRESS ret = new WS_ADDRESS();

        <... mapping logic goes here>

        return ret;
    }

    public Address MapEntityToModel()
    {
        Address ret = new Address();

        <... mapping logic goes here>

        return ret;
    }
}

Now that I have my mappers I create a method on Address that I can pass them into, in order to facilitate transforming the data. So you can see in the code below that I have had to overload the methods because each mapper has its own types involved. This means that every time I want to add a new data source to populate the Address object I have to re-open Address and add new overload methods. Ugghhh ... no thanks you (what happened to "closed for modification"?)

public class Address
{
    public string Street1 { get; set; }
    public string Street2 { get; set; }
    public string Street3 { get; set; }
    public string Street4 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Country { get; set; }
    public string PostalCode { get; set; }
    //
    // Populate "this" instance of the Address object from data found in the mapper.
    // The "mapper" argument would have to have been instantiated with the entity it expects to map 
    // to the Domain object, Address
    //
    public Address MapToModel(DB1AddressMapper mapper)
    {
        return mapper.MapEntityToModel();
    }

    //
    // Map "this" instance of address to a new DB1_ADDRESS instance
    //
    public DB1_ADDRESS MapToEntity(DB1AddressMapper mapper)
    {
        return mapper.MapModelToEntity(this);
    }


    //
    // And now again for WSAddressMapper
    //
    public Address MapToModel(WSAddressMapper mapper)
    {
        return mapper.MapEntityToModel();
    }

    //
    // Map "this" instance of address to a new WS_ADDRESS instance
    //
    public WS_ADDRESS MapToEntity(WSAddressMapper mapper)
    {
        return mapper.MapModelToEntity(this);
    }

} 

This leads me to interfaces and generics ... which I have dabbled in for years but the lack of necessity for them has not forced me to deepen my understanding of them (which I believe holds me back).

Back to the problem at hand ... I only want two mapping methods in Address that will be "closed for modification". They need to accommodate any mapper for any data source I run into. The mapper encapsulates all the specific mapping logic and Address doesn't really care about the details. It just wants to "MapTo".

The pseudo-code solution looks something like this ...

public class Address 
{
    public Address MapToModel(EntityMapper mapper)
    {
        ...
    }

    public EntityAddress MapToEntity(EntityMapper mapper)
    {
        ...
    }
}

It seems that I could make an interface for the mappers so that all mappers will implement the same two methods ...

MapModelToEntity();
MapEntityToModel();

I start with that ...

public interface IEntityAddressMapper
{
    Address MapEntityToModel();
    T MapModelToEntity<T>(Address model);
}

You may be able to see where I start to run into trouble. Since the return type of the "MapModelToEntity" varies from data source to data source I don't know what to make this. I opt to make it a generic; those have worked for me in other areas. I press on by implementing it in my mappers in hopes that an answer will reveal itself.

public class DB1AddressMapper : IEntityAddressMapper
{
    Address MapEntityToModel()
    {
        Address ret = new Address();

        <... mapping logic goes here>

        return ret;
    }

    //
    // This is what I want but, does NOT satisfy interface
    //
    DB1_ADDRESS MapModelToEntity(Address model)  <!-- DOES NOT SATISFY INTERFACE
    {
        DB1_ADDRESS ret = new DB1_ADDRESS();

        <... mapping logic goes here>

        return ret;
    }
    //
    // This satisfies interface but is silly. The mapper already KNOWS the TYPE, that's the point.
    // Besides this means that the consumer will have to pass in the types, which is EXACTLY what 
    // I am trying to avoid.
    //
    T MapModelToEntity<T>(Address model)  
    {
        DB1_ADDRESS ret = new DB1_ADDRESS();

        <... mapping logic goes here>

        return ret;
    }   
}

I've tried a million different permutations so it's impractical to list them all here but suffice to say the closest I have come so far is the following ...



public interface IEntityAddressMapper<EntityType>
{

    EntityType MapModelToEntity(Address mode);

    void MapModelToEntity(Address model, ref EntityType entity);

    Address MapEntityToModel(EntityType entity);

    void MapEntityToModel(EntityType entity, ref Address model);
}



public class DB1AddressMapper : IEntityAddressMapper<DB1_ADDRESS>
{
    Address MapEntityToModel()
    {
        Address ret = new Address();

        <... mapping logic goes here>

        return ret;
    }

    T MapModelToEntity(Address model)  
    {
        DB1_ADDRESS ret = new DB1_ADDRESS();

        <... mapping logic goes here>

        return ret;
    }   
}

This seems to allow me to implement the Interface without a problem now, but I seem to have shifted the burden to the methods which now are breaking ...

public class Address 
{
    // *********************************************
    // ERROR - Using generic type 'IEntityAddressMapper<EtityType>' requires one type argument
    // *********************************************
    public Address MapToModel(EntityMapper mapper)
    {
        ...
    }

    // *********************************************
    // ERROR - Using generic type 'IEntityAddressMapper<EtityType>' requires one type argument
    // *********************************************
    public EntityAddress MapToEntity<EntityType>(EntityMapper mapper)
    {
        ...
    }
}

I'm spinning in circles and have been for years on this. I need to sort this out!! Any help would be greatly appreciated.

Thanks

Kit
  • 20,354
  • 4
  • 60
  • 103
Gary O. Stenstrom
  • 2,284
  • 9
  • 38
  • 59
  • How does 'mapper already KNOWS the TYPE'? It does not, you have a function with a generic parameter. Perhaps you can try with IEntityAddressMapper instead of IEntityAddressMapper. In this case MapModelToEntity doesn't need a T parameter. –  Aug 23 '19 at 22:00
  • Basically, what your MapModelToEntity says is that it can map Address to any type that you put in , which is obviously not true. For example, you probably can't handle var s = MapModelToEntity... –  Aug 23 '19 at 22:02
  • I am very open to being wrong here but the point of having separate mappers is that there would be one for each databse (entity type). Thus, it stands to reason that they know about the types they are working with. Am I missing something? – Gary O. Stenstrom Aug 23 '19 at 22:05
  • 1
    Well, in your code you have a single mapper, with a function that can map Address to any type. This is different than having one mapper for each entity type. Try putting a generic constraint on the interface instead of method. –  Aug 23 '19 at 22:07
  • I think that's got me on the right path! – Gary O. Stenstrom Aug 23 '19 at 22:24
  • Good, happy to help. –  Aug 23 '19 at 22:32

1 Answers1

2

You need to start with a generic interface that varies by two type parameters, not one. This will solve your immediate problem, but after the solution, I'd like to suggest a different approach.

Solution

Consider this interface

public interface IMapper<TModel, TEntity>
{
    TEntity MapModelToEntity(TModel source);

    TModel MapEntityToModel();
}

which allows you to create specific implementations that can be passed to your Address class:

public class DatabaseAddressMapper : IMapper<Address, DB1_ADDRESS>
{
    public DB1_ADDRESS MapModelToEntity(Address source) { ... }

    Address MapEntityToModel()
}

public class WSAddressMapper : IMapper<Address, WS_ADDRESS>
{
    public WS_ADDRESS MapModelToEntity(Address source) { ... }

    Address MapEntityToModel()
}

and modify Address so that it has a few generic methods that can accept your mapper

// only need TEntity for this generic method because we know we are an Address
public Address MapToModel<TEntity>(IMapper<Address, TEntity> mapper)
{
    return mapper.MapEntityToModel();
}

// only need TEntity for this generic method because we know we are an Address
public TEntity MapToEntity<TEnity>(IMapper<Address, TEntity> mapper)
{
    return mapper.MapModelToEntity(this);
}

With this setup, your Address class now follows the Open/Closed Principal because it can accept any mapper that supports an Address and any arbitrary type. This also decouples your Address from the other type completely, which is good.

Alternative

There's room for improvement here, and it's rather simple. Ask yourself: why does Address need to know anything about mapping at all? It's just an address. Mapping is the concern of something else: maybe the caller itself?

You can remove the methods from Address completely, and in your caller where you were calling these methods

var mapper = new WSAddressMapper();
var model = address.MapToModel<WS_ADDRESS>(mapper);
var entity = address.MapToEntity();

you can call the mapper directly.

var model = mapper.MapEntityToModel<WS_ADDRESS>();
var entity = mapper.MapModelToEntity(address);

This means your Address is now obeying the Single Responsibility Principle as well! After all it's your caller that's initiating the mapping; it is what should have this responsibility, not the address itself.

Let's Keep Going!

Why one interface that maps in both directions? After all, you're more likely to map one direction for one bit of code (e.g. saving to a database) and map the other in another bit of code (e.g. reading from a database).

Let's break the interface into two, each with only one method. I'll leave that implementation to you, but here's what you get: another SOLID pillar, the Interface Segregation Principle. Woo hoo!

Kit
  • 20,354
  • 4
  • 60
  • 103