비즈니스 성공을 위한 Java/Spring 기반 서비스 개발과 MSA 구축 #2 (프로젝트 구조 및 설계)

박주진·2021년 9월 6일
0

아래 내용은 비즈니스 성공을 위한 Java/Spring 기반 서비스 개발과 MSA 구축 강의를 기반으로 하여 정리한 내용입니다. 자세한 내용이 궁금하면 위에 강의를 참고해주세요.

좋은 코드란?

  • 비즈니스 목표를 달성해야 한다.
    • 기술은 비즈니스 가치 충족을 위한 도구일 뿐이다!
  • 가독성
    • 개발업무의 80프로 정도가 코드 읽기이다. 그럼으로 가독성이 좋아야 팀 전체의 업무 효율을 높일 수 있다.
    • 특히 도메인 로직은 코드 자체로 이해가 되어야 한다.
  • 테스트 코드 작성에 편리해야 한다.
    • 신규 기능 개발, 리팩토링, 기존 기능 변경등과 같은 작업의 안정장치 역할을 한다.
    • 테스트 코드가 설계에 대한 피드백을 줄수 있다. (테스트 코드 작성이 쉬운코드는 대체적으로 품질이 좋다.)
  • 변경에 유연해야한다.
    • 고객의 요구사항은 언제든지 바뀔수 있기떄문에 유연하게 작성되어 한다.
    • solid 객체지향 원칙을 적용하여 변경에 대응하는 설계를 도출할 수 있다.
    • 인터페이스를 잘 활용하는게 핵심이다.

DDD기반 Layer 구조

의존관계

interfaces -> application -> domain <- infrastructure

  • 계층별 의존관계는 무조건 단방향을 유지하고 계층간 호출에는 인터페이스를 통한 호출을 유지해야 한다.
  • domain 계층은 low level에 의존하지 않고 독립적으로 존재할 수 있어야 한다.

레이어별 상세 설명

  • domain
    • 업무 개념과 업무 상황에 대한 정보, 업무 규칙을 나타내는 핵심 레이어이다.
    • domain service에서는 추상화 레벨을 높여 도메인의 전체 흐름이 파악할 수 있게 하고 세부 구현은 infrastructure 계층에 위임한다.
    • domain service는 domain 계층에 선언된 인터페이스에 의존하고 infrastructure는 해당 인터페이스를 구현한 객체들로 채운다.
    • domain service에 transaction 처리를 한다.
    • domain 계층에 위치한 모든 클래스를 service라고 명칭하지 말고 소수의 주요 도메인 흐름을 관리하는 객체 들만 service라는 이름을 부여하자.
    • service를 support하는 역할을 가진 객체는 책임에 맞는 적절한 이름을 가져가자.
    • domain service 끼리는 참조를 허용하지 말자! 허용하면 domain service가 참조하는 하위 service가 늘어나게 될 가능성이 많다. 이는 의존성이 높아져 테스트 하기 힘들고 가독성도 많이 떨어진다.
    • domain 계층 안에서만 엔티티가 사용되도록 하고 도메인 계층 밖으로 벗어날때는 엔티티가 아닌 별도의 객체로 반환하자. 그래야 도메인 로직이 새어나가는 것을 방지할 수 있다.
    • Entity, Service, Command(변경 요청 정보를 담은 객체), Criteria(조회 요청을 담은 객체), Info(엔티티 정보를 담은 객체), Reader(저장소에 특정 객체를 읽어오는 것을 담당하는 객체), Store(저장소에 특정 객체를 저장한는 것을 담당하는 객체), Excutor(읽고 쓰고 여러 조합을 포함해서 실행하는 객체), Factory(복잡한 엔티티 생성 및 저장하는 객체) 등이 도메인 계층에 포함된다.
  • infrastuture
    • domain 계층에 사용되는 인터페이스를 실제 기술스택에 맞춰 구현한 객체들을 위치 시키는곳.
    • 로직 재활용을 위해 해당 계층에서는 서로 간에 참조를 허용하되 순환 참조에 방지하기 위해 상하관계를 적절하게 유지하자.
    • 해당 계층의 객체들은 @component 애노테이션을 활용하자.
  • interface
    • 사용자의 요청을 해석해서 시스템을 실행시키고 그 처리 결과를 사용자 원하는 형태로 가공해주는 역할을 하는 계층이다.
    • http APi, gRPC, 비동기 메시징과 같은 다양한 서비스 통신 기술을 받아드리고 파싱하는 로직이 위치하고 외부 통신 기술이 domain에 영향이 가지 않도록 해야한다.
    • api requset, response 정의시 제한적으로 설계해야한다. 왜냐하면 한번 공개하면 바꾸기 어렵기 떄문이다.
  • application
    • transaction으로 묵여야 하는 도메인 로직과 그 이외의 로직을 묶는 역할로 한정한다. ( 예) 주문 도메인 로직과 알림 발송 로직)
    • 필요하다면 여러 domain service 결과값을 조합해 하나의 결과값으로 변환하는 역할을 부여할 수 있다.
    • xxxFacde라는 이름을 사용한다.

권장하는 구현 방식

개발 디자인 문서를 작성하자

  • 서비스 목표, 설계, 제약 사항 등을 미리 고려하여 시행 착오를 줄일 수 있다.
  • 개발 디자인 문서를 기반으로 동료들의 피드백을 받아 더 나은 방향으로 진행할 수 있다.
  • 인수인계시 서비스 파악에 도움이 될 수 있다.

테이블이 아닌 핵심 도메인 도출 먼저

  • 테이블은 객체를 영속화 하는 그릇 정로만 생각하자.
  • 요구사항과 제약조건을 고려하여 핵심 도메인 객체 도출에 집중하고 테이블 구조는 구후에 고려해야 한다.

변수명, 메서드명에 신경을 쓰자

  • 메서드명, 변수명은 가독성에 많은 영향을 준다.
  • 현업에서 사용하는 보편적인 언어를 사용하라.
  • 네이밍 규칙을 정해라.

api 명세시 request와 response의 프로퍼티 값은 필수 값만 유지한다

  • 이는 rest api 물론 특정 객체의 공개된 메서드도 포함한다.
  • request parameter가 많다는건 책임이 많다는 신호로 보고 분리를 고려해라.
  • response에다 불필요한 응답을 포함하고 있으면 추후에 불필요한 내용을 response에서 제거하기는 어렵다.

setter 최소화

  • 필수값은 객체 생성시에 받도록하고 도메인 상태 변경시에는 setter가 아닌 적절한 메서드명을 가진 메서드를 생성해야 한다.
  • 적절한 메서드명 가진 메서드로 추출하는게 도메인의 정합성 유지와 가독성 측면에서 setter 보다 더 좋다.

transaction의 사용 범위 설정을 신중히 결정하라

  • 비싼 리소스임으로 최대한 작은 범위로 잡아야 한다.
  • transaction의 범위에 3rd party 서비스 호출이 포함된다면 timeout 설정은 필수이다.
  • 3rd party 서비스 호출이 너무 오래 걸린다면 transaction내에 포함하지 않고 보상 트랜색션등 다른 방법을 고려해야 한다.

모든 도메인 객체를 DB에 저장하지 않아도 된다

  • 도메인 로직 관리하는 vo는 테이블에 저장하지 않아도 된다.

try-catch는 필요한 경우만 사용하자

  • 코드양의 늘어나 가독성에 영향을 줄 수 있음으로 exception 발생후 별도의 작업 (복구 작업)이 필요한 경우만 사용하자.

객체 모델링시 꼭 필요한 상태만 선언하자

  • 도메인 관심을 가지는 정도로만 추상화한다.
  • 지나친 세분화는 도메인 파악을 어렵게 하고 코드 구현을 복잡하게 한다.

테스트 코드를 작성하자

  • 코드변경후 테스트 코드를 통해 예상치 못한 버그를 찾을 수 있다.
  • 개발자에 안정감을 가져다주고 빠르게 기능을 구현하는데 도움을 준다.

목표한 기능을 달성하는 동작하는 코드가 우선이다

  • 비즈니스 가치를 적절한 시간안에 전달하는 것도 매우 중요함을 잊지말자.
  • 너무 처음부터 완벽하게 하려고 하기보다는 기능을 먼저 구현후 리팩토링을 거쳐 코드의 질을 높이는 방식으로 접근하는것이 좋다.

무조건 정석대로 구현할 필요는 없다

  • 약속한 시점에 기능을 런칭하는 것은 매우 중요함으로 적절한 trade off가 필요하다. (시간 vs 코드 품질)
  • 하지만 코드의 품질을 낮추는 중복코드와 하드코딩은 특정 시점에 반드시 정리해야 함을 전재로 선택하야 한다.

궁금증

  • 서비스 끼리 참조를 허용하지 않는다면 다른 서비스에서 다른 서비스를 호출해야 하는 경우 어떻게 대처해야 하나? 참조하려는 서비스의 모든 로직을 그대로 가져와서 사용해야 하는걸까?

  • interface 또는 application 레이어에서는 infrastructrue 레이어를 바로 의존하면 안될까?

  • 복잡한 조회 전용 쿼리들은 어디에 위치시켜야 하나? reader에서 구현하면 되는걸까? 그리고 이를 어떻게 관리해야 할까? 다른 cud 로직과 동일하게 interface -> application -> domain -> infra와 같은 흐름을 타도록 해야할까?

  • infrastuture 계층에는 domain 계층에 선언된 인터페이스를 구현한 객체만 위치해야 하나?

0개의 댓글