DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 968,547 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Creating smarter DTOs with fusion objects in C#
Vic
Vic

Posted on • Updated on

Creating smarter DTOs with fusion objects in C#

It is often necessary to transmit data from different entities within our code, anonymous (dynamic) objects destroy any possibility of debugging and therefore the best possibility is to create DTOs (Data Transfer Objects).

With the amount of data that must be transported, the amount of DTOs always grows and creates an environment where they become out of sync with the real models, probably due to a technical failure of the developer in the face of so many classes to take care of.

What are "fusion objects"?

  • In my first attempts (outside of any book, documentation or external article), "fusion objects" are entities that inherit from Tuple<T1, T2, T3...> and hides the Item1 and Item2 members allowing the developer to expose only the needed members while keeping a direct relationship with the reference entities.
  • In my second validation case, the theory of the topic before - if implemented with Tuples - is invalid due to some reasons that will be explored below with independent, composed objects.

First Attempt

Let's suppose I have two entities in my database: UserAuthentication (which holds the user's authentication information, including sensitive data) and UserItems (which holds any kind of random item referring to that user).

class UserAuthentication 
{
    public long Id { get; set; }
    public long ItemsId { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
class UserItems
{
    public long Id { get; set; }
    public long UserId { get; set; }
    public short ItemAQuantity { get; set; }
    public short ItemBQuantity { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Now, we have to find a way to get specific fields from these models in a presentable way to the final user - without exposing sensitive or useless data.

The common way is making a standard DTO model, like the example:

class UserSomeActionDTO 
{
    public string Username { get; set; }
    public short ItemAQuantity { get; set; }
    public short ItemBQuantity { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Okay, it's great, isn't it? Just no. Now, let's suppose you need to change the UserAuthentication template or the UserItems. You will also have to change the DTO model directly as well as all your builds, following a difficult maintenance practice and attention issues that can occur frequently.

With "fusion objects" you can control the types that will compose the object explicitly as well as its quantity (if there are two, three or more types that will compose the DTO) and expose only the necessary fields in a safe way.

class UserSomeActionDTO : Tuple<UserAuthentication, UserItems> 
{
    //Constructor will call default tuple constructor
    public UserSomeActionDTO(
        UserAuthentication item1, 
        UserItems item2
    ) : base(item1, item2) {}

    //We will overwrite "Item1" and "Item2" field to make them private
    private new readonly UserAuthentication Item1;
    private new readonly UserItems Item2;

    //Now we can expose only the needed fields to that object
    public string Username
    {
        get => Item1.Username;
    }
    public short ItemAQuantity
    {
        get => Item2.ItemAQuantity;
    }
    public short ItemBQuantity
    {
        get => Item2.ItemBQuantity;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, can we simple instance this DTO calling new UserSomeActionDTO(myUserAuthentication, myUserItems) and use it immediately? No, there are two core problems with the code.

  • There is no way to modify the accessibility property from Tuple items to secure the object, this go against basic OOP rules and there is no exception even using the new keyword to override the field in C#
  • There is no way to properly deserialize the object and work with it in "real-life" web applications or similar due to NullException issues with System.Text.Json, this means that another DTO would be needed to make that functional

Composing fusion objects

After the first version of this article, via suggestive implementation of a functional DTO model that takes into account instance commented by @carlhugon we can have a smaller and reliable version without using Tuple<T1, T2, T3>...:

class UserSomeActionDTO
{
    //We are declaring the fields private to prevent data exposure
    private readonly UserAuthentication _item1;
    private readonly UserItems _item2;

    //Constructor that calls the union with two required types
    public UserSomeActionDTO(
        UserAuthentication item1, 
        UserItems item2
    )
    {
        _item1 = item1;
        _item2 = item2;
    }

    //Now we can expose only the needed fields to that object
    public string Username => _item1.Username;
    public short ItemAQuantity => _item2.ItemAQuantity;
    public short ItemBQuantity => _item2.ItemBQuantity;
}
Enter fullscreen mode Exit fullscreen mode

With the creation of the model, in relation to standard DTOs that are widely used by devs, we have the following advantages:

  • The model doesn't need default instance where each field needs to be manually placed by the dev - in my experience the default instantiation model of DTOs has pretty much always been there and caused a lot of problems and delays in development
  • Added security by ensuring that unwanted sensitive data will not be exposed if not explicitly selected
  • There is also more organization due to the fact that a change in any model would only require changing the DTO model, instead of each point in the code where it was instantiated

Top comments (5)

Collapse
 
carlhugom profile image
Carl-Hugo Marcotte

First of all, private new readonly do not hide the members. Using a .NET 7 console application, if you write the following code, the output will be 123:

var dto = new UserSomeActionDTO(new() { Id = 123 }, new());
Console.WriteLine(dto.Item1.Id);
Enter fullscreen mode Exit fullscreen mode

That said, in you opinion, what are the advantages of inheriting for the Tuple<UserAuthentication, UserItems> class versus composing the DTO class normally, like the following?

class UserSomeActionDTO
{
    private readonly UserAuthentication _item1;
    private readonly UserItems _item2;

    public UserSomeActionDTO(UserAuthentication item1, UserItems item2)
    {
        _item1 = item1;
        _item2 = item2;
    }

    public string Username => _item1.Username;
    public short ItemAQuantity => _item2.ItemAQuantity;
    public short ItemBQuantity => _item2.ItemBQuantity;
}
Enter fullscreen mode Exit fullscreen mode

Moreover, I strongly suggest to take nulls into consideration in that code.

Collapse
 
victoriarose profile image
Vic Author

Good evening Carl, after a few days doing the code validation, I could see that in this comment you are absolutely right, I was able to improve the context of my article with the solution without tuples, after some tests, I also discovered that there are many problems with the serialization of the version of DTOs with tuples and this is a problem. I made some changes and corrections to the content and gave proper credit.

Collapse
 
mfedatto profile image
MaurΓ­cio Fedatto • Edited on

Never heard of this design pattern. It's quite odd to me. It feels detached from large applications reality. In 22 years as developer I've never seen a DTO out of sync due to technical failure of the developers. What does happens very often is that DTOs are meant to transfer data from A to B and when some entity on A changes the DTO can't be immediately affected, since it is also attached to B and it will have to get updated too. Although, quite frequently the change is needed for some compatibility break, conflict, or major incident, witch demands a straightforward hot fix, witch also means the need of backwards compatibility (not breaking B).
Do you have any extra data or article about this design pattern or its performance impact on DTOs?

Collapse
 
victoriarose profile image
Vic Author

Today, I made some changes to the core that directly impact how the creation of DTOs is explained and also its model, it is interesting to reread (at least the changed parts), since now, the advantages have also been introduced to the article.

Collapse
 
carlhugom profile image
Carl-Hugo Marcotte

The model doesn't need default instance where each field needs to be manually placed by the dev [...]

You can achieve this in several ways using C#, including the following:

class SettingPropertyDTO
{
    public SettingPropertyDTO(string username, short itemAQuantity, short itemBQuantity)
    {
        Username = username;
        itemAQuantity = ItemAQuantity;
        itemBQuantity = ItemBQuantity;
    }

    public string Username { get; }
    public short ItemAQuantity { get; }
    public short ItemBQuantity { get; }
}
Enter fullscreen mode Exit fullscreen mode

You could also use record classes (C# 9.0) to make your DTOs immutable and shorter to write (removing a lot of plumbing code), like this:

record class RecordDTO(string Username, short ItemAQuantity, short ItemBQuantity);
Enter fullscreen mode Exit fullscreen mode

Using C#11 (preview at this time), you could also leverage the required keyword like this:

class RequiredPropertyDTO
{
    public required string Username { get; init; }
    public required short ItemAQuantity { get; init; }
    public required short ItemBQuantity { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

No matter what technique you employ, the DTO is now back to having only one responsibility: being a plain object used to transfer data (see the single responsibility principle; the S in SOLID for more info).


In case you really need to encapsulate the construction logic somewhere, you could create a factory instead (or a mapper or whatnot else), like this:

class SomeFactory
{
    public static SettingPropertyDTO CreateDTO(UserAuthentication item1, UserItems item2)
        => new(item1.Username, item2.ItemAQuantity, item2.ItemBQuantity);
}
Enter fullscreen mode Exit fullscreen mode

Doing this distributes the responsibilities between the DTO (data object) and the construction of the DTO (the factory in this case), which can lead to a more flexible system. For example, you can reuse one piece (DTO) or the other (construction logic) independently and with limited impact on one another.

For the record, I'm no big fan of static members when I can avoid them, but the CreateDTO method was a straightforward way of explaining my point.


To conclude, it seems the problems you are trying to solve with "fusion objects" come from elsewhere in your application design and have nothing to do with the DTOs themselves.

🌚 Friends don't let friends browse without dark mode.

Sorry, it's true.