해당 포스트는 MSA에 대한 개념과 주요 기술에 대해 알아보고 실무에 적용했었던 내용을 정리하는 포스트입니다.
비즈니스 로직은 시스템의 목적인 비즈니스 영역의 업무 규칙, 흐름, 개념을 표현하는 용어입니다. 비즈니스 로직은 소프트웨어 시스템, 어플리케이션 등을 개발할 때 중요한 기반을 제공합니다.
관심사의 분리는 시스템의 각 영역이 처리하는 관심사가 분리되어 잘 관리되어야한다는 것을 의미하며 이는 시스템을 이해하고 변경하기 쉽게 만듭니다. 관심사의 분리 원칙에 기인한 것이 바로 모듈화 및 계층화이며 관심사의 분리 원칙에 따라 각 영역은 고유 관심사에 의해 분리되고 집중되어야 합니다.
관심사의 분리 측면에서 비즈니스 로직은 기술보다 오랫동안 지속되고 안정적이어야 할 핵심 영역이므로 기술에 영향을 적게 받게끔 비즈니스를 표현하는 비즈니스 로직 영역과 기술 문제를 처리하기 위한 기술 영역은 분리되는 것이 좋습니다.
이렇게 관심사의 분리 원칙에 맞게 어플리케이션의 외부(기술 영역) / 내부(비즈니스 로직 영역)를 구조화하고 유연성과 확장성을 가지게끔 설계한 아키텍처들이 있는데 대표적으로 레이어드 아키텍처, 헥사고날 아키텍처,
클린 아키텍처가 있습니다.
레이어드 아키텍처는 소프트웨어를 여러 개의 레이어 / 논리적인 논리적 계층으로 구분한 아키텍처를 의미합니다. 이는 시스템을 모듈화하여 유지보수성, 확장성, 유연성을 향상시키는 데에 중점을 둡니다.
(참고 : 티어는 물리적 장비를 의미하고 레이어는 티어 내부의 논리적인 분할을 의미합니다.)
전통적인 3계층 아키텍처와 N계층 아키텍처가 대표적이며 3계층 아키텍처의 경우 다음과 같이 레이어를 구분합니다.
레이어드 아키텍처의 주요 특징은 1) 소프트웨어 / 어플리케이션을 여러 레이어로 나누고 각 레이어가 독립적으로 개발, 테스트, 유지보수 될 수 있도록 하며 2) 각 레이어는 특정 책임을 담당하게끔 하고 3) 레이어 간 통신을 인터페이스나 추상 클래스를 통해 진행하도록 하여 구현 세부 사항을 숨기고 높은 수준의 모듈화를 제공합니다.
참고로 4계층 아키텍처에서는 데이터 레이어가 퍼시스턴스 레이어 / 데이터베이스 레이어로 세분화됩니다.
레이어드 아키텍처의 규칙은 다음과 같습니다.
// 인터페이스 호출을 통한 다형성 추구
// Layer A
public class A {
private final B b;
public A(B b) {
this.b = b;
}
public void bFunc1() {
b.func1();
}
public void bFunc2() {
b.func2();
}
}
// Layer B의 인터페이스
public interface B {
public void func1();
public void func2();
}
// B의 구현체 1
public class BType1 implements B
@Override
public void func1() {
// 세부 구현
}
@Override
public void func2() {
// 세부 구현
}
}
// B의 구현체 2
public class BType2 implements B {
@Override
public void func1() {
// 세부 구현
}
@Override
public void func2() {
// 세부 구현
}
}
다만 레이어드 아키텍처에서 의존성 역전 원칙은 만족하나 개방 폐쇄 원칙(확장에는 열려 있어야 하고 변경에는 닫혀 있어야 하며 개체의 행위는 확장할 수 있어야 하나 개체를 변경해서는 안됨)이 위배되는데 그 이유는 다음과 같습니다.
프레젠테이션 레이어, 비즈니스 로직 레이어, 데이터 레이어의 3계층으로 예시를 들어보자면 다음과 같습니다.
// 비즈니스 로직에서 정의한 인터페이스
public interface BusinessService {
}
// 데이터 액세스 계층에서 정의한 인터페이스
public interface DataRepository {
List<Data> findAll();
}
// 비즈니스 로직 서비스 구현체
// 데이터 액세스 계층에서 정의한 인터페이스에 직접 의존하고 있음
// 데이터 액세스 계층의 인터페이스가 확장되거나 변경될 경우 영향을 받을 수 있음
@Service
public class DataService {
private final DataRepository dataRepository;
@Autowired
public DataService(DataRepository dataRepository) {
this.dataRepository = dataRepository;
}
// 데이터 엑세스 수행
public List<Data> getAllData() {
return dataRepository.findAll();
}
}
이러한 문제를 해결하려면 의존성 역전 원칙(의존성을 역전시켜 상위 수준 모듈이 하위 수준 모듈에 의존하는 전통적인 의존성의 흐름을 뒤집는 것)을 적용하여 데이터 액세스 계층에서 정의한 인터페이스를 비즈니스 로직 계층으로 이동시켜 데이터 액세스 계층의 구현체가 비즈니스 로직 계층의 인터페이스를 바라보게 해야 합니다.
// 비즈니스 로직에서 정의한 인터페이스
public interface BusinessService {
List<Data> getAllData();
}
// 데이터 엑세스 계층에서 정의한 인터페이스
public interface DataRepository {
List<Data> findAll();
}
// 비즈니스 로직 서비스 구현체
// 비즈니스 로직은 자신의 인터페이스에만 의존 (BusinessService)
// 데이터 액세스 계층은 비즈니스 로직에 제공해야 할 인터페이스를 구현하여 제공해야 함 -> 의존성이 역전됨
@Service
public class DataServiceImpl implements BusinessService {
private final DataRepository dataRepository;
@Autowired
public DataServiceImpl(DataRepository dataRepository) {
this.dataRepository = dataRepository;
}
// 데이터 엑세스 수행
@Override
public List<Data> getAllData() {
return dataRepository.findAll();
}
}
현대 어플리케이션에서는 프레젠테이션 계층, 데이터 액세스 계층 말고도 다양한 인터페이스를 필요로 하며, 어플리케이션을 호출하는 다양한 시스템의 유형과 어플리케이션과 상호작용하는 다양한 저장소가 존재하여 단방향 계층 구조에서는 이러한 점을 지원하기가 어렵습니다.
이러한 한계를 극복하고자 다른 아키텍처들이 등장하게 되었는데 이 다음에 소개딀 헥사고날 아키텍처와 클린 아키텍처가 대표적입니다.
도메인 주도 설계로 시작하는 마이크로서비스
마이크로서비스 패턴