Backend Development 7 min read

Understanding DTO, BO, PO, and VO in Backend Development

This article explains the concepts of Data Transfer Object (DTO), Business Object (BO), Persistent Object (PO), and Value Object (VO) in backend development, shows how they differ and interact across layers, and provides Java code examples and a global configuration to filter null fields during serialization.

Top Architecture Tech Stack
Top Architecture Tech Stack
Top Architecture Tech Stack
Understanding DTO, BO, PO, and VO in Backend Development

When discussing DTO, BO, PO, and VO, it is recommended to first read the referenced articles for background.

These model abstractions address the lack of model abstraction and boundary control mentioned in the linked posts.

DTO, BO, PO, VO definitions

DTO (Data Transfer Object): Used to transfer data between server and client or between services. It contains only the data needed for a specific business scenario and no business logic, reducing coupling between layers.

BO (Business Object): Represents objects in the business logic layer, encapsulating data related to a business operation and the operations on that data. A BO may aggregate multiple entity attributes and handle complex business logic.

PO (Persistent Object): Maps directly to a database table record, with fields corresponding to table columns. It is typically used in the persistence layer (e.g., Hibernate, JPA) for CRUD operations.

VO (Value Object): Used in the view layer to encapsulate data presented to the user. It may correspond to a database table or be customized for UI needs, aiming to expose only necessary data.

Code examples

Below is an example of a PO class:

@Data
public class User implements Serializable {
    private Long id;
    private String username;
    private String password;
    private String identityCard;
    private String gender;
    private String location;
    private String userImage;
    private String phoneNumber;
    private String createTime;
    private String updateTime;
    @TableLogic
    private int isDelete;
}

The corresponding DTO:

@Data
public class UserDTO implements Serializable {
    private Long id;
    private String username;
    private String password;
    private String identityCard;
    private String gender;
    private String location;
    private String userImage;
    private String phoneNumber;
}

Business objects for login and update operations:

@Data
public class UserLoginBO implements Serializable {
    private String username;
    private String password;
}

@Data
public class UserUpdateBO implements Serializable {
    private Long id;
    private String username;
    private String password;
    private String identityCard;
    private String gender;
    private String location;
    private String userImage;
    private String phoneNumber;
}

In the backend flow, a UserDTO serves as the entry point receiving module parameters, the BO filters and controls the boundary before the Service layer, and finally the data is transformed into a PO before reaching the infrastructure layer.

Note that a BO can also be used directly as a single object (e.g., UserLoginBO or UserUpdateBO ) when only model conversion is needed without value filtering.

Why VO is omitted

The author believes that DTO can handle the responsibilities typically assigned to VO, and a global configuration can filter out null fields from DTOs, avoiding the need for an extra VO layer when sending data to the frontend.

Configuration to ignore null values during JSON serialization:

@Configuration
public class GlobalConfig extends WebMvcConfigurationSupport {
    @Override
    protected void configureMessageConverters(List
> converters) {
        super.configureMessageConverters(converters);
        converters.add(mappingJackson2HttpMessageConverter());
    }

    /**
     * Custom MappingJackson2HttpMessageConverter
     * Implements: ignore null values, allow empty fields
     */
    private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return new MappingJackson2HttpMessageConverter(objectMapper);
    }
}

Source: juejin.cn/post/7334691453833166848

Javabackend developmentDTOModel LayerPOVOBO
Top Architecture Tech Stack
Written by

Top Architecture Tech Stack

Sharing Java and Python tech insights, with occasional practical development tool tips.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.