1

Update: I added some diagrams to help understanding

I had a discussion with a colleague about two different approaches in mapping data objects. I'd like to get your opinion on pros and cons between the two approaches.

Some background information. We have two external services. One is reached over SOAP, the other over REST. They return objects of similar nature (postal delivery points). As they are different external services, the data objects are different. For example (oversimplifying the actual data objects):

public partial class ParcelShopA
{
    public string City { get; set; }
    public string Address1 { get; set; }
} 

and service 2 might be:

public partial class ParcelShopB
{
    public AddressB Address { get; set; }
}

public partial class AddressB
{
    public string Street { get; set; }
    public string HouseNumber { get; set; }
    public string City { get; set; }
}

Now, the objective is to combine the responses of the two services into a single unified format and expose that. So we have our own class:

public class UnifiedAddress
{
    public string Address { get; set; }
    public string City { get; set; }
}

Here's a diagram showing the initial problem, before applying any solution:

diagram of initial problem

Approach 1: My idea was to create separate mapper per case. In my mind, it's simple functional programming case, where we want to go from object A to object B.

public class ConverterA
{
    public UnifiedAddress ConvertToUnified(ParcelShopA p)
    {
        // TODO implement
    }
}

and the same for B:

public class ConverterB
{
    public UnifiedAddress ConvertToUnified(ParcelShopB p)
    {
        // TODO implement
    }
}

Now, keep in mind, each service will be implemented within a separate code project (probably a separate microservice as well), so all logic will be contained there.

Here's how that solution would look like:

solution 1

Approach 2: The alternative approach was something like this:

  • Define an interface like IParcelShop that defines the requirements for all parcel shops
  • Define a class that converts from IParcelShop to the unified address
  • Since we're in C# and we have partial classes, implement IParcelShop in each data object.

So we'll have something like:

public interface IParcelShop
{
    string GetAddress();
    string GetCity();
}

and the mapper converts from IParcelShop:

public class Mapper
{
    public UnifiedAddress Convert(IParcelShop parcelShop)
    {
        return new UnifiedAddress
        {
            City = parcelShop.GetCity(),
            Address = parcelShop.GetAddress()
        }
    }
}

Now, since we have partial classes, we can add this functionality to the data objects of the services, e.g. for the first parcel shop it would be:

partial class ParcelShopA : IParcelShop
{
    public string GetAddress() => Address1;
    public string GetCity() => City;
}

and for the second maybe:

partial class ParcelShopB : IParcelShop
{
    public string GetAddress() => Address.Street + " " + Address.HouseNumber;
    public string GetCity() => Address.City;
}

Here's a diagram showing the second solution:

second solution

In my mind the first approach is much cleaner because it does not touch the data objects, does not rely on partial classes and contains the mapping logic in a single place.

What arguments would you present to someone to convince him/her in favor of one or the other solution?

2

I would go with a unified model that contains all the required info regarding the postal addresses from the different external services. This will be used in the "internals" of your application.

Your clients for the external services with the required interfaces will include a translation layer from the internal representation to the required schema by the service and vice versa. IMO conceptually this is where they belong since I expect each external service will require a different translation.

This way you have a separation/abstraction of your core application which deals with a unified model and the interaction with the external parties. Changes in the external service will not affect the rest of the application - only the client. You can add/remove clients from external services easier.

0

I think that I can try to answer my own question.

First of all, drawing the diagrams was very important, which is a lesson on its own. It's easier to discuss over something visible, at least for me. Perhaps this is the most important lesson for me.

The first solution implements the mapping logic inside the microservice it uses it. It does not modify any other class and it does not add logic to the DTOs. As the external services are different, they have different DTOs, so the mapping logic is going to be different by definition.

So, the problems with the second solution. It introduces an extra level of mapping essentially, that doesn't add any value. The Mapper has very little to do, as the IParcelShop is already modeled to be very close to the desired final class (UnifiedAddress). The specialized mapping logic is still inside the microservices, but this time it maps to a made-up interface.

The other problem is that this solution modifies data objects like ParcelShopA. First of all, the only reason this is possible is because the programming language supports it (C# partial classes). In my mind this is a violation of both Single Responsibility Principle and Open/Closed Principe.

It violates SRP because ParcelShopA has one purpose and that's to be a DTO for an external service. Suddenly, it also knows how to map to a different data type. What if we want to map to something else as well? Are we going to add more to it?

It also violates Open/Closed principle. As I understand it, adding code to a class means modifying it, not extending it. Here, due to the partial class, one might say that this is extension and not modification. But I think that you're clearly modifying it; the fact that the modifications can live on a separate .cs file doesn't change that fact.

The overall problem is that the solution is trying hard to apply an OOP solution to something that isn't solved by OOP. Transforming from one class to another is a mapping, a pure function. Functional programming is more suitable here.

Taking microservices into account, one problem that both solutions have is the existence of a shared project. The Shared DTO project exists there only to enforce schema consistency between the three microservices. It is convenient because it makes sure all three projects use the same contract in the messages they exchange. However, the second solution makes the problem worse by adding more logic into that project. From what I understand, copy-pasting the UnifiedAddress class into the 3 projects is something that people are advocating in a microservice architecture. I like this article about this topic: Sharing Code Between Microservices?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.