💻 프로젝트/DevThink

개발일지 - (1) 전체적인 구조와 도메인

미미누 2022. 1. 16. 18:50

현재 데브싱크(DevThink) 서비스를 구축하고 있다.

데브싱크는 개발자들이 사용하는 성장형 커뮤니티 앱이다.

자세한 내용은 아래 링크를 통해 확인할 수 있다!

https://devthink.notion.site/devthink/185af3c83f4743f292067f206bc95acf

 

안녕하세요, '데브싱크' 팀 입니다

데브싱크(DevThink) 팀을 소개합니다!

devthink.notion.site

 

 

 현재 백엔드 개발자로 참여중인 나는, POST(커뮤니티 글 쓰기 부분)을 담당하고 있다.

스프링 부트/JPA를 통해 앱 개발 프로젝트를 진행하고 있는데, 전체적인 구조는 다음과 같다.

 

 

JPA에서 Dto랑 Entity의 개념이 들어가는데, 

Entity 클래스란 JPA에서 실제 데이터베이스의 테이블과 매칭되는 클래스이고, 

DTO는 그저 계층간 데이터 교환이 이루어 질 수 있도록 하는 객체이다.

 

Entity와 DTO를 분리하는 이유는 다음과 같다.

 Entity의 값이 변하면 Repository 클래스의 Entity Manager의 flush가 호출될 때 DB에 값이 반영되고, 이는 다른 로직들에도 영향을 미친다. 때문에 View와 통신하면서 필연적으로 데이터의 변경이 많은 DTO 클래스를 분리해주어야 한다.

 

참고자료 : https://velog.io/@ohzzi/Entity-DAO-DTO%EA%B0%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C

 

Entity, DAO, DTO가 무엇이며 왜 사용할까?

개인적으로 Spring Boot를 가지고 CRUD를 구현한 Todo-list를 만들어면서, Spring Data JPA를 사용하게 되었다. JPA를 사용하면서, 생전 처음 보는 Entity, DAO, DTO 개념을 사용하게 되었는데, 앞으로 계속 많이

velog.io

 

 

 처음에는 PostController에서 클라이언트에게 값을 반환할때 Dto가 아닌 Entity 형태로 반환했는데, 

비지니스 로직과 상관없는 곳에서 자원의 속성이 실수로 변경되는 것을 막기 위해 Controller 내에서 builder 형태로

반환받은 entity를 Dto 형태로 변환하여 client에게 넘겨주었다.

 

/**
     * 게시글의 정보를 받아 게시글을 dto 데이터로 변환하여 반환합니다.
     * @param post 게시글 정보
     * @return 입력된 dto 데이터로 변환된 값
     */
    private PostDto getPostData(Post post)
    {
        if(post == null)
            return null;

        return PostDto.builder()
                .user_id(post.getUser_id())
                .category_id(post.getCategory_id())
                .title(post.getTitle())
                .content(post.getContent())
                .status(post.getStatus())
                .build();
    }
}

 

PostService 내에서는 Dto를 entity로 변환하여 PostRepository에 접근하기 위해서 ModelMapper의 일종인

dozermapper를 이용해보았다. 

 

 인터넷에 찾아본 바로는 Dto내에서 builder 메소드를 반환하여 변환해주는 방법을 사용하는데,

ModelMapper를 사용하는게 더욱 간단한 것 같다. 

 

public Sample toEntity() {
    Sample.builder()
          .company(this.company)
          .description(this.description)
             ...
          .build();
    }

[빌더 패턴]

 

public class PostService {
    private final PostRepository postRepository;
    private final Mapper mapper;

    public PostService(PostRepository postRepository, Mapper mapper) {
        this.postRepository = postRepository;
        this.mapper = mapper;
    }

    /**
     * 전달받은 게시글 데이터로 새로운 게시글을 DB에 저장합니다.
     * @param postDto 게시글 데이터
     * @return  사용자의 정보를 DB에 저장.
     */
    public Post savePost(PostDto postDto){
        Post post = mapper.map(postDto, Post.class);
        return postRepository.save(post);
    }
    
  // 생략
  }

dozermapper를 쓰는 것은 간단하다. mapper 변수를 final로 선언해주고

생성자 의존관계 주입으로 스프링 빈에 등록만 하면 쉽게 사용할 수 있다.

mapper.map(변환할 Dto, entity.class)로 변환하여 entity 값을 반환받을 수 있다.

 


[도메인 설계 부분은 다음과 같다]

 

JPA 엔티티를 작성할때 중요한 점은 Setter를 사용하면 안된다는 것이다. 

하지만 Setter를 무분별하게 남용하다 보면 여기저기서 객체(엔티티)의 값을 변경할 수 있으므로 객체의 일관성을 보장할 수 없습니다라고 나와있다. 

 

참고:

https://velog.io/@aidenshin/%EB%82%B4%EA%B0%80-%EC%83%9D%EA%B0%81%ED%95%98%EB%8A%94-JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%9E%91%EC%84%B1-%EC%9B%90%EC%B9%99

 

JPA 엔티티 작성 - Setter 금지

엔티티를 작성함에 제가 생각하는 몇가지 원칙(?)이 있습니다.그중 엔티티(객체)의 Setter 사용 금지 원칙(?) 에 대해 알아보겠습니다.엔티티를 작성할 때 습관적으로 모든 필드에 Setter를 생성하는

velog.io

 

Entity나 사용할때 @NoargsConstructor(AccessLevel.PROTECTED) 어노테이션을 사용하여

기본 생성자의 접근 제어를 PROTECTED로 설정해놓게 되면 무분별한 객체 생성에 대해 한번 더 체크할 수 있는 수단을 위해 사용했다.

 

Entity 인 것을 명시하기 위해 애노테이션으로 @Entity를 명시하였다.

@Bulider.default를 사용하면 builder를 통해 객체를 생성할때 기본값으로 status를 active로 판단하여 넣어준다.

status는 빈 값이면 안되는 값이기 때문에 @builder.default를 사용하여 기본 값으로 설정해주었다.

 

 

 

BaseTimeEntity를 따로 class를 생성하여 PostEntity에서 BaseTimeEntity를 상속하여 명확성을 중시하였다.