java – An illustrative example of the difference between DTO, POCO (POJO) and Value Object

Question:

Inspired by the article on the differences between DTO, POCO and Value Object on Habrahabr: DTO vs POCO vs Value Object , as well as by the question POCO vs DTO .

There are no specific examples anywhere. Please provide a specific example with a small description (or also an example) of where and how to use it and for what purpose.

UPD

Great answers. Thanks to all.

Another small question about using POCO. When and how rational is it to push logic into objects? For example, I have a service layer that returns POCO, what methods can I insert there? Let's say I need to validate the Custom, ok, I made the Validate method in POCO, until I need to go into the database for validation – everything is fine, but as soon as it is needed, the idea no longer seems so good. Or am I wrong? Now I have an application where almost all the actions are performed by the business layer, in the models there are only simple methods of the GetFullName type, and in fact I operate with DTOs. So, how to keep track of that fine line "what's in POCO, what's in the service" or in general "all the logic in services, operate DTO"?

Answer:

Let's imagine some online store. This store has a web interface and an application server that handles the logic. A certain user wants to place an order. To do this, he needs to perform a series of actions: add the desired products to the cart and confirm the order.

To do this, the Order class can exist on the application server:

public class Order
{
    private ItemDiscountService _itemDiscountService;
    private UserService _userService;

    public Order(ItemDiscountService itemDiscountService, UserService userService)
    {
        _itemDiscountService = itemDiscountService;
        _userService = userService
    }

    public int Id { get; set; }
    public List<Item> Items { get; set; }
    public decimal Subtotal { get;set; }
    public decimal Discount { get; set; }

    public void AddItem(Item item)
    {
        Items.Add(item);
        CalculateSubtotalAndDiscount();
    }

    public void CalculateSubtotalAndDiscount()
    {
        decimal subtotal = 0;
        decimal discount = 0;
        foreach (var item in Items)
        {
            var currentCost = item.Cost * _itemDiscountService.GetDiscountFactor(item) * _userService.GetCurrentUserDiscountFactor();
            subtotal += currentCost;
            discount += item.Cost - currentCost;
        }

        Subtotal = subtotal;
        Discount = discount;
    }
}

This class contains data and the logic for changing them. It does not inherit from any specific class from third party library or from any third party class and is simple enough – Plain Old CLR / Java Object .


When the user adds something to the cart, this information is passed to the application server, which calls the AddItem method in the Order class, which recalculates the cost of goods and the discount, thereby changing the order status. You need to display this change to the user, and for this you need to pass the updated state back to the client.

But we cannot just pass an instance of our Order or a copy of it, since it depends on other classes ( ItemDiscountService , UserService ), which in turn may depend on other classes that may need a database connection, etc.

Of course, they can be duplicated on the client, but then all our logic, the DB connection string, etc. will be available on the client, which we do not want to show at all. Therefore, to just pass the updated state, we can make a special class for this:

public class OrderDto
{
    public int Id { get; set; }
    public decimal Subtotal { get; set; }
    public decimal Discount { get; set; }
    public decimal Total { get; set; }
} 

We can put in it the data that we want to transfer to the client, thereby creating a Data Transfer Object . It can contain absolutely any attributes we need. Including those that are not in the Order class, for example, the Total attribute.


Each order has its own identifier – Id , which we use to distinguish one order from another. While an order with Id = 1, containing 3 items, can exist in the memory of the application server, the same order can be stored in the database, with the same identifier, but containing 5 items. This can occur if we read the order status from the database and changed it in memory without saving the changes to the database.

It turns out that despite the fact that some values ​​for the order in the database and the order in the memory of the application server will be different, it will still be the same object, since their identifiers are the same.

In turn, the value of the cost is 100 , the number of the identifier is 1 , the current date, the name of the current user – "Петрович" will be equal to similar values ​​only when these values ​​completely coincide, and nothing else.

That is, 100 can be equal to only 100 , "Петрович" can only be equal to "Петрович" , etc. And it does not matter where these objects will be created. If their values ​​are exactly the same, they will be equal. Such objects are called Value Objects .

In addition to the already existing Value Objects of type decimal or string you can create your own. In our example, we could create an OrderPrice type and put the Subtotal , Total and Discount fields there.

public struct OrderPrice
{
    public decimal Subtotal;
    public decimal Discount;
    public decimal Total;
}

In there is a suitable opportunity for this to create value types that are compared by value and, when assigned, are copied entirely.


UPDATE Regarding the updated question (although this is really a separate big question, as Discord pointed out):

When we develop an application, we work with a specific subject area. This subject area can be expressed in the form of some model and actions that change the state of this model. All this can be represented as a set of classes. Such classes contain both data (in the form of class fields) and actions that manipulate this data (in the form of methods).

In principle, there are no restrictions on the placement of data or methods by classes. You can put everything in one class and it will work great. The main problem is that such code will be more difficult, and therefore more expensive to maintain. Since everything will be intertwined, any changes can introduce a bunch of errors, etc. Therefore, in order to achieve a "cheaper" code, we begin to somehow structure it, divide it into modules, etc.

We can decompose data into some classes, and methods into others, and this will also work and will be even more modular. But it can still carry a number of disadvantages. Looking at a bunch of data it may not be obvious what might be happening to it or who might need it. It's the same with a bunch of methods. Therefore, to make it even more convenient, you can decompose the data into classes by somehow grouping them in an understandable way. It's the same with methods. Data of order, user, product, etc. can become separate classes just like classes with corresponding methods. It will be even more modular and easier to understand. But any approach has its pros and cons.

For example, in our online store there are various products, the logic of calculating the price of which can be quite complicated. Let's imagine that there is a base class Item , and many derived classes:

public class Item
{
  public int Id {get;set;}
  public string Name {get;set;}
  public decimal BaseCost {get;set;}
  public decimal Cost {get;set;}
}

public class Boots : Item { ... }
public class Shirt : Item { ... } 
public class Pants : Item { ... }

Since our logic is in separate classes, ItemCostService imagine that there is an ItemCostService class that can calculate the cost of a product. Then, due to the presence of a large number of different conditions, it may look something like this:

public class ItemCostService
{
  public decimal CalculateCost(Item item)
  {
    if(item is Boots)
    {
      item.Cost = ...
    }
    else if (item is Shirt)
    {
      item.Cost = ...
    }
    else if ....  
  }
}

And there can be many such places in the program, where, depending on the specific type of product, there should be different behavior. Of course, this will all work. But, as soon as we have a new type of product, or the logic of processing an existing type of product changes, we will have to change the code in a large number of places where such conditions are present. It is more difficult than changing everything in one place, it takes longer and is fraught with the fact that you can forget to do something.

In this question, we are talking about languages, the main paradigm of which is OOP. This means that there is a ready-made infrastructure that supports the basic principles of OOP. To follow this paradigm and benefit from the off-the-shelf infrastructure, we can change our class by adding cost calculation logic to them, changing it as needed in derived classes:

public class Item
{
  ...
  public virtual void CalculateCost() { ... }
}

public class Boots : Item
{
  public override void CalculateCost() { ... }
}

Each derived type will be able to define the logic of its behavior on its own. All of it will be in one place, next to the data. And which of the specific methods to call will be determined by the infrastructure, saving us from this headache. In this example, this approach will be more convenient, since we will no longer need to create a bunch of if 's throughout the code, which will only simplify the program and make changes easier.

Well, again – it all depends on the situation. There is no silver bullet and in different cases it is worth using different approaches that will be cheaper in each specific situation. A little more about OOP and the rest you can see in my article here .

Scroll to Top