본문 바로가기

Back-End/Spring

Spring layered architecture와 객체지향적으로 개발하기

글을 쓰게 된 계기

토비의 스프링 9-3장. 스프링 웹 어플리케이션 아키텍처 단원을 읽다가 내가 이때까지 얼마나 생각없이 개발을 진행하였고 객체지향을 신경쓰지 않으면서 개발을 진행하였는지 반성하게 되었다.

 

그래서 공부를 통해 다음에 대해 이해하려 했다.

  • Spring layered architecture에서 책임을 잘 분리하는 방법
  • Spring layered architecture에서 객체지향적으로 개발하는 방법

내가 하고 있던 잘못된 개발

Presentation, Service, Data Access layer를 나름 잘지키면서 개발한다고 '착각'하고 있었다.

  • Presentation Layer : 사용자 화면을 구성하는 코드 + request/response와 관련된 코드
  • Service Layer: 비즈니스 코드는 다 여기다 작성하자! ( 가장 큰 문제 )
  • Data Access Layer : 데이터베이스와의 접속을 관리 ( JPA 덕분에 잘 지키고 있었음 )

여기 가장 큰 문제였던 것은 Service layer에 모든 비즈니스 코드를 집어 넣자였다. 객체지향적인 생각을 하지 않고 모든 코드를 집어넣음으로써 완전 절차지향적으로 짜고 있었던 것이다.

 

 

예를 들어, 특정 회원의 게시물 중 특정 날짜 이후의 게시물만 가져오는 로직을 개발해보자!

 

// Service Layer
@Service
@Transactional
public class ContentService {
  @Autowired
  private MemberRepository memberRepository;

  @Autowired
  private ContentRepository contentRepository;
  ...

  // 특정 회원이 작성한 게시물중 특정 시간 이후의 게시물만 가져온다.
  public List<Content> getContentsOfMemberAfterSpecificDate (Long memberId, DateTime date) {
      Member member = memberRepository.getMember(memberId);

      List<Content> contents = member.getContents();
      List<Content> specificDateContents = Lists.newArrayList();

      // 특정 날짜 이후의 게시물만 가져오는 Business Logic
      for (Content content : contents) {
          // 특정 날짜 이후인지 체크하는 Business Logic
          DateTime contentCreatedAt = content.getCreatedAt();

          if (contentCreatedAt.isAfter(date)) {
              specificDateContents.add(content);
          }
      }

      return specificDateContents;
  }

  ...
}
// Member Domain Object
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Member {

    @Id
    @GeneratedValue
    @JsonProperty
    private Long id;

    @Column(unique = true, nullable = false, length = 20)
    @JsonProperty
    private String memberId;

    @Column(nullable = false, length = 20)
    @JsonProperty
    private String password;

    @Column(nullable = false, length = 20)
    @JsonProperty
    private String name;

    @Column(nullable = true, length = 30)
    @JsonProperty
    private String email;

    @Column(nullable = false)
    @JsonProperty
    private Boolean deleted = false;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "member")
    @JsonIgnore
    private List<Content> contents = new ArrayList<>();

    ...

    // Domain Object 에서는 단순히 컨텐츠를 가져오는 역할만 수행
    public List<Content> getContents() {
        return contents;
    }

}

// Content Domain Object
@Entity
public class Content {

    @Id
    @GeneratedValue
    @JsonProperty
    private Long id;

    @ManyToOne
    @JoinColumn(foreignKey = @ForeignKey(name = "fk_content_member"), name = "member_fk_id")
    private Member member;

    @Lob
    @Column(nullable = false)
    @JsonProperty
    private String content;

    @Column(nullable = false)
    @JsonProperty
    private DateTime createdAt;

      // Content Object 에서는 단순히 게시물 생성날자를 가져오는 역할만 수행
    public DateTime getCreatedAt() {
        return createdAt;
    }
}


출처: https://wckhg89.tistory.com/13 [줌구의 개발일기]

조금 더 객체지향적으로 생각해보기

비즈니스 로직에 2가지 책임이 들어가 있다. 이 책임을 각각의 객체에게 위임하는 것은 어떨까? 즉, 객체에게 일을 시키는 것이다.

public List<Content> getContentsOfMemberAfterSpecificDate (Long memberId, DateTime date) { 
	Member member = memberRepository.getMember(memberId); 
    List<Content> contents = member.getContents(); 
    List<Content> specificDateContents = Lists.newArrayList(); 
    
    // 1. 특정 날짜 이후의 게시물만 가져오는 Business Logic 
    // todo : Member 객체가 Contents를 가져오는 것이니 Member 객체에 일을 위임하면 어떨까? 
    for (Content content : contents) { 
    	// 2. 특정 날짜 이후인지 체크하는 Business Logic 
        // todo : Content 객체가 특정 날짜 이후를 체크하는 것이니 
        // Content 객체에게 일을 위임하면 어떨까? 
        DateTime contentCreatedAt = content.getCreatedAt(); 
        if (contentCreatedAt.isAfter(date)) { 
        	specificDateContents.add(content); 
        } 
    } 
    return specificDateContents; 
} 

 

// Member Domain Object
@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Member {
  ...

  public List<Content> getContentsAfterSpecificDate (DateTime date) {
      List<Content> specificDateContents = Lists.newArrayList();

      for (Content content : this.contents) {
          // Content 객체에게 특정날짜 이후인지 확인을 위한 메시지를 던짐
          if (content.isAfterCreatedDate(date)) {
              specificDateContents.add(content);
          }
      }
      return specificDateContents;
  }
  ...
}

// Content Domain Object
@Entity
public class Content {
  ...

  // 특정날짜 이후의 게시물인지 체크하는 Business Logic
  public Boolean isAfterCreatedDate (DateTime date) {
      return this.createdAt.isAfter(date);
  }

  ...
}


출처: https://wckhg89.tistory.com/13 [줌구의 개발일기]

Service, Domain Object의 역할은 어떻게 나눌까?

Service Layer가 비즈니스 담당이라고 해서 절대 만능 클래스를 만들어서는 안된다.

  1. 다른 Service를 조합하거나 DAO를 연결하는 역할
  2. Transaction, Cache와 적용과 같은 infra 적용
  3. Service는 가볍게
  4. Service에 핵심 비즈니스 로직을 구현하기 보다는 로직의 상태값을 가지고 있는 모델이 담당해야 한다.

딱 이정도만 하고 도메인 객체도 클래스이기 때문에 State뿐만 아니라 자신의 상태를 토대로 행위를 하는 Action을 정의해주는게 맞다고 생각한다. 

 

즉, 모든게 객체이며 절차지향적인 코드 대신 객체에게 일을 시키는 식의 흐름으로 코드를 작성해야겠다!

'Back-End > Spring' 카테고리의 다른 글

AOP란  (0) 2019.06.20
Spring MVC에 관하여  (0) 2019.06.07
Spring Security 아키텍쳐  (0) 2019.05.21
Spring boot 2.0에 관하여  (0) 2019.05.20
스프링 CORS  (0) 2019.05.20