내가 쓰는 백엔드구조

빵동·2023년 4월 21일

1. 좋은 백엔드?

좋은 백엔드 구조는 뭘까에 대한 답은 거의 정해져 있는 거 같다.

비즈니스 로직을 구현하면서 확장 가능하고 유지 보수하기 좋은 구조.

그렇다면 비즈니스로직을 구현 하면서 확장가능하고 유지보수 하기 위해선 어떤 것들이 필요할까? 이런 조건들을 잘 만족시키기 위해 어떤 개념들을 가지고 백엔드 구조를 가지고 가면 좋을지 고민했던 내용들을 정리해본다.

2. 객체지향

그 친구 일 똑바로 잘 한대?

객체지향에 대한 내용을 자세히 적으려면 끝도 없겠지만은 내가 생각하는 가장 중요한 내용은 다음과 같다. 나의 요청을 받은 객체는 어떤 방식으로 일을 하던 내가 원하는 결과를 준다. 이다. 이 내용은 도메인간의 상호작용, 계층간의 상호작용, 시스템간의 상호작용 모두에게 적용되는 이야기다. 1인분만 잘 하면 세상이 편해진다.
-> 객체지향사실과오해를 읽어보자

3. 함수형프로그래밍

액션과 계산을 분리한...

함수형 프로그래밍에서 주요하게 이야기하는 것은 소중한 원본 데이터는 함부로 건드리면 안돼...라는 느낌이다. 외부에서 변경가능한 데이터를 직접 조작한다면 연산중에 데이터가 변경되어 올바른 연산 결과가 나오지 않는다. 그렇기 때문에 연산전에 데이터를 복사하고 계산하고 반환하는 구조를 가진다.
코드는 1. 액션 2.계산 3.데이터 구분할 수 있는데 각 항목들은 다음과 같은 특징을 가진다.
1. 액션 : 실행 시점에 영향을 받고 연산의 결과가 외부에도 영향을 미치는 것
2. 계산 : 동일한 정보가 들어온다면 항상 같은 결과값을 주는 것.
3. 데이터 : 비즈니스 로직에 필요한 계산된 결과를 저장해 놓은 것.

이러한 분리는 앞으로 설명할 계층구조에 큰 영향을 미친다. 미리 간단하게만 말하자면 아래와 같다.

도메인객체는 비지니스 로직을 구현하기 위한 데이터를 포함하고, 따로 저장은 해놓지 않았지만 필요한 비즈니스로직 데이터를 계산할 수 있는 도메인 함수, 그리고 호출 순서와 시간이 중요한 비즈니스 의사결정 순서인 액션으로 구분되어 있는 것이다.

->함수형코딩을 읽어보자

각각의 내용들이 어떤 폴더 어떤 파일이름을 가지고 어떻게 동작하는지 앞으로 이어서 설명하겠다.

4. 클린 아키텍처 기반의 폴더 구조

내가 찾는 코드를 한번에 찾아가기 위해

코드는 잘 짜야한다. 협업을 위해? 아니? 일주일뒤에 똑같은 코드를 볼 나를위해..

클린 아키텍처의 개요는 소프트웨어 설계 원칙으로, 유지보수성과 테스트 용이성을 높이기 위해 도입되었다. 클린 아키텍처는 소프트웨어의 구조를 계층화하고, 각 계층간의 의존성을 관리함으로써 변경에 유연하고 테스트가 쉬운 시스템을 구축하는 것을 목표이다.

클린아키텍처하면 가장 많이 보는 그림인데 해당 내용을바탕으로 폴더 구조를 만들어 봤다.

각 폴더의 역할 및 책임

Adapter : 외부와 통신하는 곳.
Application : 어플리케이션 단 비즈니스요구 사항을 처리하는 곳.
Entity(Domain) : 도메인 비즈니스요구 사항을 적는 처리 하는 곳.

어뎁터->어플리케이션->도메인의 흐를만 간단하게 나와있는데 다양한 조합이 가능하다.


등등 비즈니스 로직을 수행하기 위해 외부와 소통하고 계산하는 조합은 무수히 많다.

5. DDD 관점에서의 비즈니스 로직

핵심 로직은 얼마 안된다.

사실 핵심적인 비즈니스로직은 외부 인프라 관련 코드 없이도 작성이 가능해야 한다. Application은 도메인 로직에서 필요한 외부 자료를 제때 가져다 주는 역할만 하면 된다. Adapter를 통해...

6. 계층간의 이동에서 변환되는 객체

집 왔으면 손 씼고 옷 갈아입어야지

DTO, Domain, Entity의 차이
  • 나는 DTO객체는 Adapter-Controller에서 요는 외부 요청시 들어오는 데이터를 DTO라고 부르며 취급하고 있다.
  • Entity객체는 DB에서 온 데이터다.
  • Domain객체는 DTO와 Entity에서 변환이되며 Domain객체는 해당 도메인을 위해 특별한 자료를 생성할 수 있는 메서드를 가진다.

DTO와 Entity는 연산기능 없음!

DTO는 외부에서 전달되는 데이터 객체, Entity는 DB에서 전달된 객체이다. DTO나 Entity의 데이터를 기반으로 특정 기능을 가진 도메인 객체가 생성되는 것이다.

새로운 유저를 생성하기 위해 HTTP요청으로 JSON형태로 요청되어 Controller에서 반환되면
Body : {"name":"newUser","age","20"}
Controller는 해당 요청을 처리하고 전달된 Json을 객체 형태로 생성한다.

class UserDTO{
 name:"newUser,
 age:"20"
}

이후 서비스 로직으로 넘어가서 createNewUser 함수가 호출된다고 할 때 DTO는 파라미터 입력으로 전달되고
서비스로직은 전달받은 DTO객체를 User객체로 변환한다.

createNewUser(userDto:UserDTO){
  const user = new User(userDTO);
  //이렇게 생성된 유저는 `유저`만의 고유한 기능을 사용할 수 있다.
  
  if (user.isAdult()){
   console.log("어른임") 
  }else{
  	console.log("어른 아님")
  }
  
  //처럼 유저 도메인의 함수로 비즈니스 로직을 작성할 수 있다.
}

마찬가지로 DB에서 값을 읽어와서 유저의 비즈니스 로직을 실행할 때도 Entity객체를 Domain객체로 변환하는 작업을 한다.

getUserById(userId:string){
  const userEntity = this.userRepository(userId);  
  const user = new User(userEntity);
  //이렇게 생성된 유저는 `유저`만의 고유한 기능을 사용할 수 있다.
  
  if (user.isAdult()){
   console.log("어른임") 
  }else{
  	console.log("어른 아님")
  }
  
  //처럼 유저 도메인의 함수로 비즈니스 로직을 작성할 수 있다.
}

7. 헥사고날 아키텍처 기반의 인바운드와 아웃바운드가 명확히 구분되는 구조

현관문으로 오시는 손님 어서오시고요? 이만 저는 뒷문으로 나갑니다..

인바운드와 아웃바운드의 개념

인바운드 : 내가 만든 어플리케이션으로 요청이 들어옴.
아웃바운드 : 내가 만든 어플리케이션에서 요청이 나감.

인바운드 아웃바운드 처리는 Adapter에서 담당하며 들어오는 요청은 in.Controller에서 처리하고 나가는 요청은 Out.Client, Out.Repository에서 담당한다.

하나의 서비스에서 여러가지 HTTP엔드포인트를 만들어야 한다면 다음과 같은 구조를 가질 수 있다.

  a.controller ┐
  b.controller ├> Service
  c.controller ┘

또는 하나의 서비스에서 AWS S3버킷을 사용하는 API와 AWS RDS를 사용하는 API, 총 2개의 Client와 1개의 Repositoty를 사용한다면 다음과 같은 구조를 가질 수 있다.

         ┌> S3.client
Service -├> RDS.client
         └> Repository - DB             

따라서 하나의 서비스가 여러개의 endpoint, client, repo, eventQueue 등등 인바운드 아웃바운드 처리가 필요한 모든 것들을 Adapter에서 관리한다면 코드 분리가 용이하다.

확장가능성 및 의존성을 줄이기 위한 의도적인 불편함

만약 두개의 서비스가 서로 연관되어 비즈니스 로직을 처리해야 하면 어떻게 해야할까?

user와 book은 서로 다른 도메인이기 때문에 각자의 서비스 로직과 Repository로직 등등 모든것이 분리되어 있다고 생각해보자.

상호 참조 하고 싶지만 서로의 서비스 로직은 각각의 의존성을 의존성을 가지고 구현되어있기 때문에 User가 Book을 import해야 한다면 User는 Book의 의존관계를 모두 가지게 되는 문제가 있다.

그리고 해당 관계로 각자에서 import를 한다면 의존성 순환 문제가 발생할 것이다.

그래서 각각의 도메인은 Client(얻는 요청, 아웃바운드)와 Controller(요청된 자료를 처리, 인바운드)를 통해 소통하는 것이다.

이렇게 구현한다면 일단 하나의 백엔드에서 각각 모듈을 분리해서 사용할 수 있고 서버를 확장해야 한다면 모듈을 분리해서 운영하면 된다.

위 그림을 네트워크를 포함해서 표현한다면 아래와 같다.

만약 User모듈과 Book모듈을 각각 다른 서버로 분리해야 한다면 다음과 같은 구조가 될 것이다.

8.실전 예제: 헥사고날 아키텍처와 DDD를 활용한 백엔드 프로젝트 구현

NestJS, fastAPI 에서 기본 탬플릿은 어떤 구조일까

추후에 참고 레포를 링크하겠습니다.

참고자료
객체지향사실과오해
함수형코딩
도메인주도설계로시작하는마이크로서비스개발
https://www.youtube.com/watch?v=saxHxoUeeSw
키워드 : hexagonal architecture folder structure,

2개의 댓글

comment-user-thumbnail
2023년 4월 26일

오호 헥사고날 아키텍쳐에 대해서 오늘 처음 들어봤습니다. 나중에 강연한번 해주세요. :)

보면 테스트에 용이한 구조를 많이 사용한다고 보여지는데 테스트를 잘 활용하고 계신지 어떤식으로 하고 계신지 궁금합니다~

1개의 답글