[MSA 알아보기] 2. 레이어드 아키텍처 (Layered Architecture)

mrcocoball·2023년 12월 6일
0

Architecture

목록 보기
2/5

해당 포스트는 MSA에 대한 개념과 주요 기술에 대해 알아보고 실무에 적용했었던 내용을 정리하는 포스트입니다.

1. 비즈니스 로직과 아키텍처

비즈니스 로직이란

비즈니스 로직시스템의 목적인 비즈니스 영역의 업무 규칙, 흐름, 개념을 표현하는 용어입니다. 비즈니스 로직은 소프트웨어 시스템, 어플리케이션 등을 개발할 때 중요한 기반을 제공합니다.

관심사의 분리와 비즈니스 로직, 그리고 아키텍처

관심사의 분리시스템의 각 영역이 처리하는 관심사가 분리되어 잘 관리되어야한다는 것을 의미하며 이는 시스템을 이해하고 변경하기 쉽게 만듭니다. 관심사의 분리 원칙에 기인한 것이 바로 모듈화 및 계층화이며 관심사의 분리 원칙에 따라 각 영역은 고유 관심사에 의해 분리되고 집중되어야 합니다.

관심사의 분리 측면에서 비즈니스 로직은 기술보다 오랫동안 지속되고 안정적이어야 할 핵심 영역이므로 기술에 영향을 적게 받게끔 비즈니스를 표현하는 비즈니스 로직 영역과 기술 문제를 처리하기 위한 기술 영역은 분리되는 것이 좋습니다.

이렇게 관심사의 분리 원칙에 맞게 어플리케이션의 외부(기술 영역) / 내부(비즈니스 로직 영역)를 구조화하고 유연성과 확장성을 가지게끔 설계한 아키텍처들이 있는데 대표적으로 레이어드 아키텍처, 헥사고날 아키텍처,
클린 아키텍처
가 있습니다.

2. 레이어드 아키텍처

개요

레이어드 아키텍처는 소프트웨어를 여러 개의 레이어 / 논리적인 논리적 계층으로 구분한 아키텍처를 의미합니다. 이는 시스템을 모듈화하여 유지보수성, 확장성, 유연성을 향상시키는 데에 중점을 둡니다.
(참고 : 티어는 물리적 장비를 의미하고 레이어는 티어 내부의 논리적인 분할을 의미합니다.)

전통적인 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();
    }
}

한계

현대 어플리케이션에서는 프레젠테이션 계층, 데이터 액세스 계층 말고도 다양한 인터페이스를 필요로 하며, 어플리케이션을 호출하는 다양한 시스템의 유형과 어플리케이션과 상호작용하는 다양한 저장소가 존재하여 단방향 계층 구조에서는 이러한 점을 지원하기가 어렵습니다.

이러한 한계를 극복하고자 다른 아키텍처들이 등장하게 되었는데 이 다음에 소개딀 헥사고날 아키텍처와 클린 아키텍처가 대표적입니다.

Appendix. 출처

도메인 주도 설계로 시작하는 마이크로서비스
마이크로서비스 패턴

profile
Backend Developer

0개의 댓글