Search code examples
javadto

DTO to entity and vice-versa, Which layer, between the controller and the service, should handle the conversion?


I usually implement methods within DTOs to convert them into entities. I do this because I aim to keep the code of entities as clean as possible. However, I've received feedback suggesting that it's more natural for an entity to accept a DTO as a parameter and create the entity. I read the article how to convert DTO to/from Entity, but I didn't understand it well even when I read it.

DTO

public record MemberRegistRequestDto(
@NotBlank String oauthId,
@NotBlank String oauthPlatform,
@NotBlank String name,
@NotBlank String profileImg,
@NotBlank String nickname,
@NotNull int birth,
@NotBlank String gender,
@NotBlank String profession,
@NotBlank String signatureColor,
@NotBlank String contentType
) {

    public Member of() {
        return Member.builder()
                .oauthId(oauthId)
                .oauthPlatform(getOAuthProviderFromString(oauthPlatform))
                .name(name)
                .profileImg(profileImg)
                .nickname(nickname)
                .birth(birth)
                .gender(gender)
                .profession(profession)
                .signatureColor(signatureColor)
                .profileImg(profileImg)
                .build();
    }

...

}

ENTITY


@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@Table(indexes = @Index(name = "oauth_id", columnList = "oauthId", unique = true))
public class Member extends BaseTimeEntity implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String oauthId;
    @Enumerated(EnumType.STRING)
    private OAuthProvider oauthPlatform;
    @Column(nullable = false)
    private String name;
    private String profileImg;
    @Column(nullable = false)
    private String nickname;
    private Integer birth;
    private String gender;
    private String profession;
    @Column(nullable = false)
    private String signatureColor;
    // 로그인 정보 식별 값, 프로필 사진, 필명, 이름, 성별, 생년월일, 이메일
    
    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<Adate> adates;

...

}

Convert Dto to Entity (Service Layer)


    @Transactional
    public void registMember(MemberRegistRequestDto memberRegistRequestDto) {
        Member member = memberRegistRequestDto.of();
        memberRepository.save(member);
    }

The suggestion is to have the entity accept the DTO as a parameter.

var member = Member.of(memberRegistRequestDto);
  1. Which layer is best? Controller or Service I implemented the pattern where the controller passes the DTO to the service and receives it back. This is because I see the controller as the layer that receives input from the user and passes it on to the service.

I've read many articles on Stack Overflow, but I haven't been able to understand them well.

Articles I've read:


Solution

  • One approach to your problem is through a mapper class.

    In the DTO pattern, you have the domain model, which contains all the business logic classes, and the DTOs, which are simple POJOs that allow you to expose only what you need in a single point. This pattern grants many advantages, such: reducing round trips to the server, minimizing network overhead, and decoupling the domain model from the presentation layer.

    However, to convert an object of the domain model to a DTO, you should use a mapper class. This approach allows you to decouple entities from DTOs, as an entity doesn't have (and shouldn't have) any knowledge of the internal implementation of a DTO and vice versa. This design also obeys the Law of Demeter (or Principle of least knowledge) where a class should know only as much as it needs to know.

    Also, the conversion process usually happens in the presentation layer via a mapping class. Although, some people may argue that in some scenarios, the controller layer is still a valid point for entity-DTO conversions.

    In your case, you should have a MemberMapper class with a service that maps a Member entity to a MemberRegistRequestDto DTO, and a second method that reconstructs a Member from a MemberRegistRequestDto.

    public class MemberMapper {
        public static MemberRegistRequestDto toMemberRegistRequestDto(Member m) {
            MemberRegistRequestDto dto = new MemberRegistRequestDto();
            /* mapping logic */
            return dto;
        }
    
        public static Member toMember(MemberRegistRequestDto dto) {
            Member m = new Member();
            /* mapping logic */
            return m;
        }
    }
    

    Here is an article from Baeldung that I think could be very useful. It introduces the DTO pattern and offers an example using a mapper service: https://www.baeldung.com/java-dto-pattern