(멋쟁이사자처럼_백엔드스쿨플러스) Day32 스프링 리스너를 통한 종속성 제거
모놀리식 아키텍처에서 마이크로서비스 아키텍처 변환
- 모놀리식에서 마이크로 서비스로 전환하는 단계는 다음과 같다.
서비스 -> 모듈 -> 종속성 분리 -> 마이크로 서비스
역할 기반 엔티티로 종속성 분리하기
- GIT 레포
- 모놀리식 아키텍처는 하나의 애플리케이션으로 모든 기능을 처리하는 방식이다.
- 기본 스프링 세팅
모듈로 분리하기
- GIT 레포
- post는 멤버와 연관관계로 종속성을 가지고 있다.
post와 member를 모듈로 분리한다.
1 2 3 4 5 6 7
public class Post extends BaseEntity { private String title; private String content; @ManyToOne private Author author; }
member을 post 도메인에서 Author라는 entity로 변경한다.
1 2 3 4 5 6 7 8 9 10 11
@Entity @NoArgsConstructor @AllArgsConstructor @Builder @Getter @Setter @Table(name="member") public class Author extends BaseEntity { @Column(name="nickname") private String writer; }
member 등록하기
- GIT 레포
- 테스트를 위해 member를 등록한다.
member 종속성 분리하기
모놀리식 아키텍처
- 모놀리식 아키텍쳐에서는 MemberService를 통해 Member를 받아 Post에 Author로 저장 할 수 있다.
이는 각 domain 간에 종속성이 발생한다.
1 2 3 4 5 6 7 8
Member member1 = memberService.join("user1", "1234", "user1").getData(); Member member2 = memberService.join("user2", "1234", "user2").getData(); Member member3 = memberService.join("user3", "1234", "user3").getData(); // member domain에 종속성 발생 Post post1 = postService.write(member1, "title1", "content1").getData(); Post post2 = postService.write(member2, "title2", "content2").getData(); Post post3 = postService.write(member3, "title3", "content3").getData();
종속성 분리하기
- Post의 Authoer를 Member가 아닌 프록시 객체를 생성하여 DB에서 직접 가져온다.
- 이를 위해서 EntityManager의 getReference를 활용한다.
- Memebr의 Id를 참조 하기 때문에 종속성을 완전히 분리한 것은 아니다.
각 결합 정도를 비교하면 다음과 같다.
1 2 3 4 5 6 7 8
// Member 전체 객체를 직접 사용 Author author = new Author(member); // Member의 ID만 사용하고, 프록시로 지연 로딩 Author author = entityManager.getReference(Author.class, member.getId()); // 순수 ID값만 사용(궁극적 목표) Author author = new Author(memberId);
일반적인 엔티티 조회
1 2 3
Author author = entityManager.find(Author.class, id); // 즉시 DB 조회 발생 // SELECT * FROM author WHERE id = ?
프록시를 통한 지연로딩
1 2 3 4 5 6 7 8
// 프록시 객체 생성 (DB 조회 없음) Author author = entityManager.getReference(Author.class, member.getId()); // 이 시점까지는 DB 조회 없음 Order order = Order.builder() .author(author) // ID만 필요해서 DB 조회 발생 안함 .build(); // 이 시점에 실제 DB 조회 발생 System.out.println(author.getName()); // DB 조회 발생!
이를 통해 PostService에서 MemberService에 종속성을 줄일 수 있다.
1 2 3 4 5 6 7
Author author1 = postService.of(member1); Author author2 = postService.of(member2); Author author3 = postService.of(member3); Post post1 = postService.write(author1, "title1", "content1").getData(); Post post2 = postService.write(author2, "title2", "content2").getData(); Post post3 = postService.write(author3, "title3", "content3").getData();
PostCount 등록하기(member기반)
- GIT 레포
- 모놀리식으로 PostCount 증가는 PostService에서 MemberService를 호출하여 증가 시킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//member entity에 post 증가 로직 추가
@Setter(PRIVATE)
private long postCount;
public void increasePostCount() {
postCount++;
}
//memberservice에 post 증가 로직 추가
@Transactional
public void increasePostCount(long id) {
findById(id).ifPresent(Member::increasePostCount);
}
private Optional<Member> findById(long id) {
return memberRepository.findById(id);
}
//postservice에 post 증가 로직 추가
public RsData<Post> write(Author author, String title, String content) {
//모놀리식으로 작성한 코드
memberService.increasePostCount(author.getId());
//....이후 return 동일
}
PostCount 종속성 제거
- GIT 레포
- postCount 증가로직을 Author에서 직접적으로 수행한다.
postService에서는 Author만으로도 postCount 증가가 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 13
@Table(name="member") public class Author extends BaseEntity { @Column(name="nickname") private String writer; @Column(columnDefinition = "BIGINT default 0") @Setter(PRIVATE) private long postsCount; public void increasePostsCount() { postsCount++; } }
역할 기반 엔티티 설계의 한계
- 역할 기반 엔티티 설계는 역할에 따라 다른 동작을 수행하는 엔티티를 설계하는 방식으로 종속성을 분리할 수 있다.
- 하지만 복잡한 엔티티를 다른 도메인에서 사용할때 복잡도가 증가하고 유지보수가 난해해진다.
스프링 이벤트
noti모듈 만들기
기본 Noti 모듈 생성
- GIT 레포
- 게시글이 생성되면 알림 로그를 출력하는 기본 모듈을 만든다.
- 현재 Post와 Noti는 종속성이 존재한다.
게시글 생성시 Noti 추가하기
- GIT 레포
Post가 발생하면 Noti 모듈에 post를 전달하여 Noti를 생성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
//PostService에서 NotiService를 호출 private void firePostCreateEvent(Post post) { notiService.postCreated(post); } //NotiService에서 Noti를 생성 @Transactional public void postCreated(Post post){ Member actor = postService.of(post.getAuthor()); List<Member> receivers = memberService .findAll() .stream() .filter(member -> !member.equals(actor)) .toList(); receivers.forEach(receiver -> { Noti noti = Noti.builder() .actor(actor) .receiver(receiver) .relTypeCode(post.getModelName()) .relId(post.getId()) .typeCode("POST") .type2Code("CREATED") .read(false) .build(); notiRepository.save(noti); } ); }
스프링 이벤트로 종속성 제거하기
- GIT 레포
기존 PostService에서 NotiService를 호출하는 방식에서 스프링 이벤트를 사용하여 NotiService를 호출한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
//PostService에서 NotiService를 호출 private void firePostCreateEvent(Post post) { notiService.postCreated(post); } // eventPublisher 주입 private final ApplicationEventPublisher eventPublisher; public RsData<Post> write(Author author, String title, String content) { // ...기존코드 eventPublisher.publishEvent(new PostCreatedEvent(this, post)); return RsData.of(post); }
- global로 사용할 수 있도록 PostCreatedEvent를 생성한다.
1
2
3
4
5
6
7
8
9
10
11
// com/ll/sbrdk/global/event;
@Getter
public class PostCreatedEvent extends ApplicationEvent {
private final Post post;
public PostCreatedEvent(Object source, Post post) {
super(source);
this.post = post;
}
}
- Noti에서 Post이벤트를 감지한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// com/ll/sbrdk/domain/noti/noti/eventListener;
@Component
@RequiredArgsConstructor
@Transactional
public class NotiEventListener {
private final NotiService notiService;
@EventListener
public void listenPost(PostCreatedEvent event){
notiService.postCreated(event.getPost());
}
}
EnablaAsync를 통해 비동기로 이벤트를 처리할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13
@EnableAsync // 클래스 레벨에 선언 @Configuration public class AsyncConfig { // 비동기 설정 } @Service public class EmailService { @Async // 메서드 레벨에 선언 public void sendEmail() { // 시간이 걸리는 이메일 발송 작업 } }
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.