5

Are data transfer objects or POJOs meant to be final or can they be extended and create hierarchies for them?
It is not clear to me if such a value class is properly designed only as a final class and what are some good examples/designs of a hierarchy of DTO classes?

  • This is exactly the use case for a mixin or trait. – winkbrace Oct 4 at 9:20
9

Update: Rewrote code in Java since this question is tagged java but this applies to any object oriented language.


If using inheritance is a means of reducing code duplication, I'm a little reluctant to have DTOs inherit from anything at all. Inheritance implies an is-a relationship between child class and the parent.

Using audit fields as an example, would you say the following statement is true?

Person is a BaseAuditFields.

It sounds kind of funny to me when I read it out loud. Inheritance in DTOs becomes problematic, because you introduce coupling between classes where each class usually represents a concrete idea or use case. DTOs sharing a common parent class implies the class hierarchy will evolve at the same time, and for the same reasons. This makes these classes harder to change without having a ripple effect cascading to classes where the new fields might not be applicable.

You'll notice this happen more often for DTOs that serialize and deserialize data from sources you have no control over, like a web service owned by another company or team.

That being said, I've found cases where processing a DTO can be abstracted away. Audit fields are a good example. In these cases I've found interfaces to be a much more robust solution than a concrete parent class, for the simple fact a DTO can inherit from multiple interfaces. Even then I keep the interface as tightly focused as I can and only define the minimal methods to retrieve or modify a small subset of fields:

public interface Auditable {
    int getCreatorId();
    void setCreatorId(int creatorId);
    Integer getUpdaterId();
    void setUpdaterId(Integer updaterId);
}

public class Person implements Auditable {
    ...
}

Now if we go back to our is-a test:

Person is an Auditable

The statement makes more sense. With the use of interfaces it becomes easier to utilize polymorphism in the classes that process DTOs. Now you are free to add and remove interfaces as you see fit to aid in processing the DTOs without affecting the serialization and deserialization to and from their source format. Because interfaces are being used you can modify the classes to reflect structural changes in the data, and have additional methods that make the DTO more palatable to the classes processing them.

Going back to audit fields, you have one web service call a property creatorId and another one calls it createUserId. If both DTOs inherit from the same interface, then processing both of them becomes easier.

Let's say you have a sales staff web service. By coincidence the property names in the XML response match the Auditable interface, so nothing extra is needed in this class:

public class SalesStaffWebServicePerson implements Auditable {
    private int creatorId;
    private Date createDate;
    private Integer updaterId;
    private Date updatedDate;

    public int getCreatorId() {
        return creatorId;
    }

    public void setCreatorId(int creatorId) {
        this.creatorId = creatorId;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Integer getUpdaterId() {
        return updaterId;
    }

    public void setUpdaterId(Integer updaterId) {
        this.updaterId = updaterId;
    }

    public Date getUpdatedDate() {
        return updatedDate;
    }

    public void setUpdatedDate(Date updatedDate) {
        this.updatedDate = updatedDate;
    }
}

The customer web service returns a JSON response and two fields are named differently than what is defined in the Auditable interface, so we add a couple of getters and setters to this DTO in order to make it compatible:

public class CustomerWebServicePerson implements Auditable {
    private int createUserId;
    private Date createDate;
    private Integer updateUserId;
    private Date updatedDate;

    // Getters/setters mapped over from customer web service JSON response

    public int getCreateUserId() {
        return createUserId;
    }

    public void setCreateUserId(int createUserId) {
        this.createUserId = createUserId;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Integer getUpdateUserId() {
        return updateUserId;
    }

    public void setUpdateUserId(Integer updateUserId) {
        this.updateUserId = updateUserId;
    }

    public Date getUpdatedDate() {
        return updatedDate;
    }

    public void setUpdatedDate(Date updatedDate) {
        this.updatedDate = updatedDate;
    }

    // Getters/setters to support Auditable interface

    public int getCreatorId() {
        return createUserId;
    }

    public void setCreatorId(int createUserId) {
        this.createUserId = createUserId;
    }

    public Integer getUpdaterId() {
        return updateUserId;
    }

    public void setUpdaterId(Integer updateUserId) {
        this.updateUserId = updateUserId;
    }
}

Sure there might be duplicated getters and setters, but behind the scenes they are working with the same fields so everyone is happy.

  • 3
    For interfaces, I typically refer to it as an "acts as" relationship. Which makes even more sense in this case. Person acts as an IAuditable. – Berin Loritsch Oct 2 at 22:56
  • I like that tweak in terminology. It keeps the relationship focused on behavior rather than data. – Greg Burghardt Oct 2 at 22:58
  • But what if something indeed falls into the is a relationship? E.g. BaseQuery class with common arguments for all queries and DBQuery / NetworkQuery / FileQuery that is more specific? – Jim Oct 3 at 10:36
  • 1
    @Jim: Then you are not dealing with a Data Transfer Object, and a base class very well might be a useful abstraction to create. This question was specifically about DTOs, though. – Greg Burghardt Oct 3 at 11:10
  • @GregBurghardt: I am confused now. DTO is a value class right? The class hierarchy I mention also serves as a value class. So why is this case not relevant? – Jim Oct 3 at 11:50
3

When dealing with DTO objects, you have to consider the limitations of your deserialization engine. There are cases where having a base object makes logical sense, but you may not be able to make use of it without jumping through hoops. Take for example the following hierarchy:

public class Observation {
    public string SensorManufacturer { get; set; }
    public string SensorEquipmentId { get; set; }
    public float Reading { get; set; }
    public Location Position { get; set; }
}

public class BizObservation : Observation {
    public float BarSetting { get; set; }
}

This hierarchy was based loosely on monitoring software I wrote for a contract a long time ago. The base class had everything needed for calculating observation accuracy, rates of change, error frequency, etc. There were several sensors which had additional information that could improve the accuracy of the calculations if present, and the software knew how to create the correct observation object based on the sensor manufacturer and equipment ID.

One of the challenges had to do with the capabilities of the library we were using to serialize and deserialize the observations since the observations had to be transmitted to a central location.

What can go wrong?

  • While all libraries could serialize our records, not all could deserialize them correctly
  • Some deserializers needed some custom helpers to select the right class

None of the problems are insurmountable, but you will likely need to add information to your message to pick the right sub class. Many JSON libraries work just fine out of the box for this, but if your wire format is not JSON then your mileage may vary.

Bottom Line

Flat hierarchies are going to give you the smoothest and least complex DTO experience.

Adding inheritance adds complexity, but some serialization/deserialization libraries handle that complexity out of the box for you. Others require you to jump through some hoops to make the system work reliably.

  • particularly in lists – Ewan Oct 3 at 10:59
  • Deserializing complex Json into a statically typed language is the bane of my life. – Omegastick Oct 8 at 3:59
2

I think a case could be made for a base pojo/poco/dto that has audit fields (i.e. WhenAdded, WhenDeleted, IsActive, etc.) If these fields are common across schemas/tables, should be okay. Also consider the ORM (if any) that you're using, and how it treats derived pojos.

Sample hierarchy:

class BaseAuditFields
{
    Date LastUpdated;
    Date WhenCreated;
    String CreatedBy;
    Bool IsDeleted;
}

class Person extends BaseAuditFields
{
    String FirstName;
    String LastName;
}

class Pet extends BaseAuditFields
{
    String Species;
    String Name;
    String FavoriteToy;
}
  • 1
    I do something similar in C#, although really what we need here is the Mixin concept rather than actual inheritance. – Graham Oct 2 at 21:01
  • @Graham: I've come across the same idea too. I ended up using interfaces rather than concrete classes, and just dealt with the code duplication. Getters and setters barely qualify as code duplication, especially in C# with auto properties. – Greg Burghardt Oct 2 at 22:44

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.