오늘은 드디어 Member 도메인을 처음 설계한 목표 구현을 완료하여 마감을 하려고한다. Member 도메인의 개발은 목표는 23.06.01 ~ 23.06.07 마감하는 것을 목표로 진행 하였으나, 생각보다 2주일이나 더 소요되었다.(총 3주) 무엇 때문에 목표한 기간보다 훨씬 더 많은 기간을 소모하게 되었는가 돌이켜본다면 분명하게 내 개발 역량 부족과 나태한 내 자신이 원인이 아닌가 싶다. 그리고 이는 다음 개발에서 부터는 같은 실수를 하지 않도록 노력할 것이다.
ConText
Member 도메인은 신발 주문 시스템에서 첫번째로 식별 및 개발한 도메인이다. Member 도메인에서는 다양한 비즈니스 로직가 추가 될 수 있지만, 나는 Member 의 생성 및 업데이트의 기능만 추가하였고, 도메인 로직으로는 Email과 Password 검증 로직만 추가하였다. 너무나도 간단한 기능이기에 3주가 아니라 3시간이면 다 할 수 있는 것 아닌가? 하는 생각이 들기도 한다.
신발 주문 시스템의 첫번째 개발 도메인으로서, 이 시작이 가장 중요했다. 나는 이번 프로젝트를 진행하면서, 프로젝트를 통해 얻고자 하는 것과 프로젝트의 목표를 미리 정하였다.
신발 주문 시스템을 계획하게 된 배경
shoes-ordering-system. Contribute to KEEMSY/shoes-ordering-system development by creating an account on GitHub.
github.com
나는 이번 프로젝트를 통해 가장 배우고자 한 내용은 아키텍처 부분이다. 나는 현재 좋은 코드에 대한 고민에서 부터 시작하여, 좋은 아키텍처에 대한 고민으로까지 고민을 확장하게 되었다. 그리고 이를 위해 지금까지 공부했던 아키텍처에 대한 지식과 관련 개발 지식을 활용하여 시스템을 설계하고 개발하였다.
나는 모놀리식 아키텍처에서 마이크로서비스 아키텍처까지 고도화하는 것을 목표로 하였다. 그리고 더 세부 사항으로는 도메인 주도 설계를 통한 경계 콘텍스트를 분명하게하고, 이에 이벤트기반 아키텍처를 설계하여, 모놀리식과 마이크로 서비스 아키텍처에서 모두 클린한(?) 아키텍처를 설계하고자 하였다. 그리고 이런 생각은 클린아키텍처를 고려하게 되었고, 클린아키텍처를 적용하게 되었다.
첫 설계 당시, 내가 설계한 목표는 도메인주도 설계를 통한 도메인 설계와 이벤트기반 아키텍처가 접목된 모놀리식 아키텍처에서 시작하여 마이크로 서비스 아키텍처로 고도화 하는 것이다.
클린아키텍처
아키텍처를 공부하기 전, 내 머릿 속 클린 아키텍처는 경계가 분명하게 나눠진(역할이 분명한) 모듈과 컴포넌트로 이뤄진 아키텍처가 클린 아키텍처였다. 그리고 책과 강의를 통해 아키텍처를 공부하면서 클린아키텍처를 헥사고날 아키텍처, 포트 앤 어댑터(Port And Adapter) 패턴의 아키텍처라고 부르는 것을 알게되었다.
계층을 나눈다 = 역할을 분명하게 한다. 라고 나는 생각한다. 그리하여 포트 앤 어댑터(Port And Adapter) 패턴이 클린아키턱처로 불리는 것이라고 생각한다. 헥사고날 아키텍처는 소프트웨어를 내부와 외부로 나누고, 내부 비즈니스 레이어로부터 시작한다.
클린아키텍처와 도메인주도 설계
도메인 주도 설계와 클린아키텍처를 비교하여볼 때, 클린아키텍처(헥사고날아키텍처)는 도메인 계층을 핵심으로 바라보면서 도메인 주도 개발(DDD)와 같은 접근법으로 바라볼 수 있다.
클린아키텍처에서 역할에 따라 분리된 도메인계층은 Entity와 Use Case 로 구성됨을 알 수 있다. 그리고 이는 도메인 주도 설계에서의 구조와 상당히 유사하다.
클린아키텍처 및 도메인주도 설계 모두 디커플링와 같은 관심사의 분리의 공통 목표로 인해 나는 클린아키텍처를 도입하기로 결정했다.
Conclusion
이전에 식별한 요구사항을 기반으로 기능개발을 진행하였다.
식별한 요구사항을 정리하면 3가지를 이야기할 수 있다.
1. 사용자 등록
2. 사용자 인증
3. 개인 정보 설정
사용자 등록 과 개인정보 설정은 개발하였으나, 2. 사용자 인증의 경우 시간을 고려하여 해당부분은 개발하지 않았다.(Order 도메인이 더 중요하다 생각했다.)
신발 주문 시스템 분석 도메인 : Member
shoes-ordering-system. Contribute to KEEMSY/shoes-ordering-system development by creating an account on GitHub.
github.com
기본적인 디렉토리의 구조는 도메인 계층 구조를 따르며, 세부 디렉토리 구성은 클린아키텍처를 준수하기 위해 다음과 같이 구성하였다.
Controller
일반전으로 우리가 알고있는 MVC 에서의 Controller 부분으로, 클라이언트 요청을 도메인 계층으로 보내는 역할을 담당한다. API의 경우 REST API를 기반으로 개발진행하였다.
Controller 계층은 도메인 계층과 통신하기 위해 ApplicationService 를 사용해야하며, 이는 input port 에 해당한다.
각 메서드에 따른 처리를 추적하기 위해 @Slf4j 어노테이션을 통해 해당 메서드가 성공적으로 진행될 경우 로깅을 진행했다.
눈치가 빠른사람은 에러핸들링이 없는데? 하는 생각을 할 것이다. 나는 스프링을 공부하면서 에러 핸들링을 @ControllerAdvice 어노테이션을 통해 전역적으로 할 수 있다는 사실을 알게되었다.
따라서 나는 해당 어노테이션을 활용하여, 다양한 도메인의 REST API 에서 사용할 수 있는 GlobalExceptionHandler 를 common 디렉토리에 생성했다. 그리고 각 도메인 별 GlobalExceptionHandler 를 생성하여 관리한다.
가장 최상단인 Cotroller 계층에 Try-Catch 를 하지 않아도 되는 것은 너무나도 깔끔하고 가독성이 증진되는 것 같아 아주 만족스럽다. 과거 Controller 계층에서 가장 최악이었던 에러핸들링 중, Controller 계층에서 비즈니스 로직이 포함되면서 중간중간 depth 가 2중 3중으로 가는 try-catch 를 보는 것이였는데, 그것과 비교하면 마음이 너무나도 편안해진다.
DataAccess
dataaccess 디렉토리 부분은 외부 시스템(DB)와 상호작용하는 output port 이다.(인프라계층 이라고 이야기할 수 있다.) 해당 디렉토리의 코드는 분리된 도메인 엔터티(Member) 를 Jpa 를 통한 DB에 저장하기 위한 작업을 진행하는 곳이라고 생각하면 된다.
@Component
public class MemberRepositoryImpl implements MemberRepository {
private final MemberJpaRepository memberJpaRepository;
private final MemberDataAccessMapper memberDataAccessMapper;
public MemberRepositoryImpl(MemberJpaRepository memberJpaRepository,
MemberDataAccessMapper memberDataAccessMapper) {
this.memberJpaRepository = memberJpaRepository;
this.memberDataAccessMapper = memberDataAccessMapper;
}
@Override
public Member save(Member member) {
return memberDataAccessMapper.memberEntityToMember(memberJpaRepository
.save(memberDataAccessMapper.memberToMemberEntity(member)));
}
@Override
public Optional<Member> findByMemberId(UUID memberId) {
return memberJpaRepository.findByMemberId(memberId)
.map(memberDataAccessMapper::memberEntityToMember);
}
}
비즈니스 로직이 간단한 만큼 해당 Repository 코드는 단촐하다. 이외에도 domain 계층에서 식별한 모든 Entity 또한 dataaccess 에서도 존재한다.(테이블로 저장하여 관리하여야 하니깐!)
Domain
가장 중요한 domain 계층이다. 도메인 계층은 크게 Core 와 Application으로 나뉜다.
Core와 Application 를 나눈 이유를 설명한다면 도메인 로직을 분리하기 위함이라고 이야기 할 수 있을 것 같다.
doamin-application 은 Controller 에서 사용되며 input port 이다. 즉, domain-core 와 연결하는 매개체이면서 domain core 로직을 분리하는 것이다. 그리하여 dto 와 mapper 가 존재한다.
이외의 helper 패키지는 MemberApplicationService 를 좀 더 캡슐화하고, 중복되는 부분을 모듈화 및 가독성을 높이기 위해 개발했다. ports 의 경우 해당 도메인의 input port 와 output port 를 선언함으로써, 보다 Input port 와 output port 를 구분했다.
domain-core의 경우 실제 도메인 로직이 존재한다. 추가적으로 설명을 한다면, 도메인 로직을 거쳐, 검증 후에 DB에 저장하기 위해 DomainService 에서는 도메인 이벤트를 생성한다. 그리고 Client 요청에 대한 응답을 위한 ApplicationService 는 Response 를 생성한다.
얼마 전 친구와의 대화 중, 도메인로직과 비즈니스로직의 차이가 무엇인가? 하는 주제로 이야기를 한 적이 있다. 당시에는 이에 대해 비즈니스로직은 실제 사업 간 존재하는 로직(할인, 쿠폰 등)에 해당한다 생각하며, 도메인 로직은 해당 할인될 금액의 조건(제품의 가격은 0원 이하가 될 수 없다.) 라고 생각한다.(엔터티(식별한 도메인)의 프로터티의 조건 이라고 생각한다.) 라고 이야기를 했다.
하지만 지금 생각해보니 비즈니스로직이 곧 도메인로직인 것 같다. 그리고ApplicationService는 비즈니스 로직이 불필요한 노출되지 않도록 하는 역할을 할 수 있고, 좀 더 깔끔한 코드가 될 수 있는 것 같다.
사실 아직도 말로는 이게 잘 안나온다.. 이말은 즉슨, 내가 아직 확실하게 이해하고 있지 못함을 의미하는 것 이라고 말할 수 있을 것 같다. 도메인주도설계와 클린아키텍처에 대한 공부를 좀 더 진행해야겠다..
domain 계층을 개발하면서, DTO 클래스 이름이 고민이 된 부분이 존재한다. 바로 Command 이다. 나는 Command 와 Request 이 두개의 이름 사이에서 고민을 많이 했었다.
과거 나는 Request-Response 의 쌍으로 인식이 되어있었다. 그런데 시스템 설계를 공부하면서, DB규모 확장을 공부하면서, CQRS(Command Query Responsibility Segregation)를 알게되었다. CQRS 에서 Command 는 시스템의 상태를 변경하거나 생성, 삭제하는 작업을 의미한다. 명령(Command) 와 읽기(Query)를 분리한다는 개념이다. 그래서 나는 추후 DB 규모확장을 고려(사실 이정도로 할지는 모르겠다.)하여 DTO 클래스의 이름을 Command 로 정하였다. 그리고 자연스럽게 조회 부분에서는 Query 를 사용하게 되었다.
Messaging
Member 도메인의 Messaging 패키지의 구성은 listener, mapper, publisher 로 구성된다. message 는 kafka 를 사용한다. 사실 kafka의 경우 Common 에서 한번에 관리를 해야할까? 하는 고민을 하기도 하였다. 하지만 각 도메인별 publisher 를 세분화 하는 것이 유지보수하는데 더 편리할 것이라고 생각하여, 각 도메인별로 messaging 패키지를 생성하기로 하였고, 각각의 publisher 를 생성했다.
나는 한 도메인(현 Member) 에서 생성하는 토픽을 모두 한 클래스에서 작성하는 것이 더 편리할까? 고민을 했었다. 하지만 한 클래스에 모두 담아두는 것은 SRP 위반이지 않을까? 하는 생각이 들었다. 그리고 토픽을 발행한다 라는 것으로 바라본다면, 하나의 클래스로 관리해도 좋을것 같다. 하지만 토픽을 발행한다 = Publisher 에 해당한다고 생각하고, 각 클래스 파일은 하나의 토픽만 발행 해야한다 생각하여 각 토픽별로 Publisher 클래스를 작성했다.
이번 개발을 진행하면서 Helper 클래스를 정말 많이 사용한 것 같다. 지난 날 나는 지금의 helper 를 service 에 같이 사용하였다. kafka의 경우 비동기 논블로킹 으로 이벤트를 발행하기 때문에 callback 메서드가 필요하여 해당 helper 클래스를 작성했다.
Member 도메인을 개발하면서 느낀점
사실 Member 도메인개발이라 하기에는 비즈니스로직이 너무나도 간단한부분만 존재한다. 하지만 배운 부분이 너무 많다.
지난 다양한 소프트웨어 관련 독서과 강의를 통 배운 내용을 많이 적용해 볼 수 있었다. 전부 다 내가 만들었다고하는 것은 거짓말이다. 이전에 공부한 내용을 참고하면서, 나에게 맞게 내가 납득이 되는 부분으로 개발을 진행했다. 그리고 이 과정에서 특히 배운 부분이 많은 것 같다.
클린 코드, 클린 아키텍처
클린 코드와 클린 아키텍처는 다양한 개발자들과 이야기를 해보고 싶은 주제이다. 이야기를 하면 결국 같은 이야기에 도달하겠지만 그래도 그 생각을 갖게된 과정이 모두 다르다고 생각하여, 해당 주제로 이야기를 해보고싶다. 그리고 나는 이번 프로젝트를 통해 클린 코드와 클린 아키텍처에 대해 이야기를 할 수 있는 Context 가 생긴 것 같다.
나는 클린 코드를 3가지로 이야기한다면, 첫번째도 가독성, 두번째도 가독성, 세번째도 가독성이라고 생각한다. 읽기 힘든 코드는 정말 다방면으로 힘들다. 그리고 가독성 부분은 코드(모듈)뿐만아니라 패키지 구조(아키텍처X)까지 확장될 수 있다고 생각한다. 그래서 나는 과거 기술계층의 패키지 구조에서 도메인 계층의 패키지 구조를 선택했다. 둘의 장단점이 분명하게 존재하겠지만, 내가 생각한 가독성부분에서는 한 도메인 안에 관련된 코드(파일)이 존재하는 것이 해당 도메인을 이해하는데 효과적이며, 유지관리 부분에서 또한 효율적이라고 생각한다. 다만, 한가지 내가 생각하지 못한 부분이 존재하는데, 바로 해당 도메인에서 분리된(될) 서브도메인의 관리 측면이다.
도메인 내부에 서브도메인이 여렇 존재할 수 있는데, 여렇 서브 도메인 중 거대해지는 도메인이 존재할 경우 과연 지금의 도메인계층으로 나눈 패키지 구조가 효율적일까? 다른 더 나은 방법은 없을까? 하는 생각인데 좋은 방법이 떠오르지 않는다.(그래도 기술계층으로 나눈 것보다는 훨씬 편하고 좋을것이라고 생각한다.) 서브도메인이 분리 될 때를 고려해서 Domain-Application 부분에 대한 신중한 설계가 필요할 듯하다.
이번 Member 도메인을 개발하면서, 클린아키텍처에 대해 더 공부하게 되었다.이전에는 클린코드 및 객체지향 프로그래밍의 연장선으로, 해당 원칙들이 아키텍처에도 적용된 것이라고 생각했다. 각 객체들이 느슨한 결합이 이뤄져야하는 것처럼, 각 계층 또한 느슨한 결합을 이뤄야 한다. 최소한의 종속성을 갖추고, 독립적으로 설계할 수 있는 것이다. 정말 느슨한 결합은 정말 말-모 아니겠는가? 코드 수준을 넘어 아키텍처까지 해당 원칙이 적용되는 것 같다. 이 느슨한 결합이 적용된 것이 Port And Adapter 패턴이라고 생각한다. 느슨한 결합을 계층에 적용한다면, 머리가 번뜩이듯 이해가 된다! 처음에는 다소 과하게 나눠진게 아닐까 생각했지만, 역시 선배 개발자들의 경험과 지혜는 대단한 것같다.(그래도 비판적 사고로, 더 나은 방법은 없을까? 내 상황에 최적의 설계는 무엇일까? 는 계속해서 생각하게 되는 것 같다. 하지만 결국엔 해당 내용으로 오게되더라..)
그리고 새로운 책도 구매하게 되었다.(자꾸만 쌓여간다..근데 해당 주제로 이야기해주는 너무 매력적인 책이 있는데 어찌 안사겠는가..) 해당 책을 통해 앞으로 개발할 다른 도메인들을 설계뿐만아니라, 앞으로의 내 개발 인생에서도 도움이 많이 되지 않을까? 하는 생각이다.
만들면서 배우는 클린 아키텍처 - YES24
우리 모두는 낮은 개발 비용으로 유연하고 적응이 쉬운 소프트웨어 아키텍처를 구축하고자 한다. 그러나 불합리한 기한과 쉬워보이는 지름길은 이러한 아키텍처를 구축하는 것을 매우 어렵게
www.yes24.com
책의 내용도 많지 않아 공부하는데 부담이 될 것 같지 않다. 그리고 무엇보다, 치킨보다 싸다. 그럼 무조건 산다.
이벤트기반 아키텍처, 그리고 인프라 요소
이번 프로젝트의 핵심 부분 중 하나인 이벤트기반 아키텍처는 정말 내가 목표를 위해서라도 이번 프로젝트에서 경험해보고 싶은 내용이다. 그리고 이번 Member 도메인을 통해 아주 살짝 발가락을 담가본것같다. 이벤트 기반 마이크로서비스 구축 책을 읽으면서, 내가 생각한 부분 외에도 더 많은 지식과 정보가 유입되면서, 현재 더 많은 고민 후에 코드를 작성하게 되는 것 같다.(근데 결과로보면 별거없다.) 어떤 이벤트가 발행되어야하고, 해당 이벤트를 어디까지 활용할 수 있으며, 내가 계획하는 이벤트 기반 아키텍처의 최종 목적지(?)에 대하여 고민해 볼 수 있었다.
Member 도메인 이벤트에는 CreatedMemberEvent, UpdateMemberEvent가 존재한다. 초기에는 각 이벤트에 대한ResponseEvent가 존재했다. 하지만, 고민을 하면서 해당 부분은 불필요한 부분이라 결정하여 삭제했다. 그 이유는 가장 마지막에 발행된 도메인 이벤트가 해당 객체의 최종 상태를 의미한다 생각했기 때문이다. 그래서 Response 이벤트를 발행이 불필요하다 판단했다. 그리고 이를 좀 더 잘 활용한다면, DB 종속성이 사라진 이벤트 스트림을 통한 서비스 관리, 진정한 이벤트 기반 아키텍처가 될 수 있지 않을까 생각해본다. 개발을 진행하면서 책을 병행해서 보았는데, 딱 이벤트에 대한 고민을 할 때, 이벤트에 대한 이야기가 나와서 좋았다. 해당 내용은 추후에 STUDY 레포에 정리를 해야겠다.
그리고 이벤트 기반 아키텍처를 위해서 반드시 필요한 이벤트 버스, kafka 가 정말 어려웠다. 사실 카프카 자체는 어렵다는 생각이 안든다.(건방진자식..) zookeeper 를 통해 분산 다해주고, 그냥 나는 가용성 및 회복성을 고려하여 카프카 클러스터를 구축하고, 코드로 연결만 하면된다. 근데 코드로 연결하는게 어렵더라.. 사람들이 개발한 코드를 내 상황에 맞게 하는것이 어려웠다. 정말 간단할 것이라고 생각했는데, 내가 미처 생각하지 못한 부분들 때문에 이번 Member 도메인을 개발하는데 거의 일주일이라는 시간을 소비했다..(kafka관련해서만 말이다..) 해당 부분에 대해 kafka 를 사용하는 주변에 지인도 없고, 공부하는 분들에게 이야기를 해도 같이 무엇이 문제일지 이야기하기가 쉽지않았다. 정말 혼자서 찾아보고, 해결하는 과정을 정말 오랜만에, 아니 처음으로 한게 아니가 싶다. 사실 카프카 강의를 사둔것이 있는데 너무 기초적인 내용이라 안보고있었다. 근데 자만하지말고 정독해야겠다..
정리
신발 주문 시스템의 첫 시작인 Member 도메인 개발을 진행하면서 지금까지 공부만 했던 많은 부분을 실제로 경험해 볼 수 있었다.
클린 코드 및 클린 아키텍처,이벤트 기반 아키텍처 및 이를 위한 도메인주도 설계, 사용해보지 못한 인프라 요소를 직접 사용해보는 경험까지 결과물은 간단(?)하지만 정말 많은 고민을 할 수 있었다. 하지만 아직 부족하다. 이건 경험이 없는 나 혼자만의 고민인 것이 너무 아쉽다.. 비록 지금은 혼자 고민하고 많이 헤맸지만, 다음에는 이 고민과 헤매는 시간이 줄어들 것이라 기대해 본다.
근데 생각해보니깐 나 부하테스트 안해봤다.. 내일 해봐야겠다.
'회고 > TIL' 카테고리의 다른 글
PR 작성, 피드백, 개선 (0) | 2023.06.22 |
---|---|
작성하자.. 문서.. 기록하자.. 문서.. (0) | 2023.06.21 |
해결되지 않는 에러.. 원인이 무엇일까? (0) | 2023.06.16 |
오늘도 여전한 에러 해결을 위한 노력과 무시하고 개발하는 나, 이벤트기반 마이크로서비스 구축 (0) | 2023.06.15 |
kafka 에러... 하루종일.... (0) | 2023.06.14 |