Back-End/Spring
Spring layered architecture와 객체지향적으로 개발하기
taehyun_kim
2019. 5. 31. 17:41
글을 쓰게 된 계기
토비의 스프링 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가 비즈니스 담당이라고 해서 절대 만능 클래스를 만들어서는 안된다.
- 다른 Service를 조합하거나 DAO를 연결하는 역할
- Transaction, Cache와 적용과 같은 infra 적용
- Service는 가볍게
- Service에 핵심 비즈니스 로직을 구현하기 보다는 로직의 상태값을 가지고 있는 모델이 담당해야 한다.
딱 이정도만 하고 도메인 객체도 클래스이기 때문에 State뿐만 아니라 자신의 상태를 토대로 행위를 하는 Action을 정의해주는게 맞다고 생각한다.
즉, 모든게 객체이며 절차지향적인 코드 대신 객체에게 일을 시키는 식의 흐름으로 코드를 작성해야겠다!