애플리케이션을 업무 규칙과 플러그인으로 구분하려면,
업무 규칙이 실제로 무엇인지 잘 이해해야 한다.
업무 규칙이란 사업적으로 수익을 얻거나 비용을 줄일 수 있어야 한다.
더 엄밀히 말해서, 컴퓨터상으로 구현했는지와 상관없이,
업무 규칙은 심지어 사람이 수동으로 직접 수행하더라도 사업적으로 수익을 얻거나 비용을 줄일 수 있어야 한다.
은행 시스템에서 업무 규칙을 예로 들어보면,
대출에 N%의 이자를 부과한다는 사실은 은행이 돈을 버는 하나의 업무 규칙으로 볼 수 있다.
이러한 규칙은 사업 자체에 핵심적이며,
규칙을 자동화하는 시스템이 없더라도 업무 규칙은 그대로 존재하기 때문에
핵심 업무 규칙이라고 할 수 있다.
핵심 업무 규칙은 보통 대출 잔액, 이자율, 지급 일정과 같은 핵심 업무 데이터를 요구한다.
핵심 규칙과 핵심 데이터는 본질적으로 결합되어있기 때문에
객체로 만드는 좋은 후보가 되고,
이러한 유형의 객체를 "엔티티"라고 한다.
엔티티
엔티티는 컴퓨터 시스템 내부의 객체로써,
핵심 업무 데이터를 기반으로 동작하는 일련의 조그만 핵심 업무 규칙을 구체화한다.
엔티티 객체는 핵심 업무 데이터를 직접 포함하거나,
핵심 업무 데이터에 매우 쉽게 접근 할 수 있다.
엔티티의 인터페이스는 핵심 업무 데이터를 기반으로 동작하는 핵심 업무 규칙을 구현한 함수들로 구성된다.
책의 예시에는 은행 대출을 뜻하는 Loan 엔티티를 예시로 보여주고 있다.
Loan 엔티티에서
principle, rate, period는 시스템에서 대출 업무 규칙을 다룰 때 필요한 데이터들이다.
위 데이터들을 이용하여 makePayment, applyIntegeres, chargeLateFee와 같은 함수를 제공하고 있다.
여기에 추가로 주문 시스템의 배송 도메인 엔티티를 아래와 같이 예시로 추가해보겠다.
@Entity
public class Delivery implements Serializable {
private static final long serialVersionUID = -32074198748172441252351L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private DeliveryGroup deliveryGroup;
@NotNull
private Integer orderId;
@NotNull
@Enumerated(EnumType.STRING)
private ChannelType channelType;
@NotNull
@Column(updatable = false)
private Integer quantity;
@NotNull
@Enumerated(EnumType.STRING)
private DeliveryMethod deliveryMethod;
@NotNull
@Enumerated(EnumType.STRING)
private Status status;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "delivery")
private DeliveryInvoice deliveryInvoice;
public void confirm() {
if (status.isConfirmed()) {
return;
}
this.status = CONFIRMED;
}
public void invoiceRegister() {
if(status.isAvailableInvoiceRegister()){
this.status = INVOICE_REGISTERED;
}
}
...
...
위처럼 대출 도메인, 배송 도메인을 보면
각각 어떤 데이터들이 핵심 업무 규칙에 포함되는지,
또 각 업무 규칙 데이터들을 어떻게 동작하여 행위(함수)들을 정의하는지 확인 할 수 있다.
개발자는 잘 설계된 엔티티를 통해서 한눈에 업무 데이터와 규칙을 집약적으로 파악할 수 있다.
우리는 엔티티를 설계하고 생성할 때,
업무에서 핵심적인 개념을 구현하는 소프트웨어는 한데 모으고,
구축 중인 자동화 시스템이 나머지 모든 고려사항과 분리시킨다.
엔티티 클래스는 업무의 대표자로서 독립적으로 존재한다.
엔티티 클래스는 데이터베이스, 사용자 인터페이스, 서드파티 프레임워크 등등에 대한 부가적인 고려사항들로 인해
오염되어서는 절대로 안된다!
이 클래스는 어떠한 시스템에서도 업무를 수행할 수 있으며,
시스템의 표현 형식이나 데이터 저장 방식, 그리고 해당 시스템에서 컴퓨터가 배치되는 방식과 무관하다.
엔티티는 순전히 "업무 규칙"에 대한 것이며, 부가적인 고려사항에 영향을 받지 않고,
독립적으로 존재하고 성장하고, 설계 해야 하는 요소이다.
시스템 설계, 개발 시에, 핵심 업무 데이터와 핵심 업무 규칙을 엔티티로 묶어서 별도의 소프트웨어 모듈로 만들어야 한다.
유스케이스
모든 업무 규칙이 엔티티처럼 순수한 것은 아니다.
자동화된 시스템이 동작하는 방법을 저으이하고 제약함으로써
수익을 얻거나 비용을 줄이는 업무 규칙도 존재한다.
이러한 규칙은 자동화된 시스템의 요소로 존재해야만 의미가 있으므로
수동 환경에서는 사용될 수 없다.
대출 애플리케이션을 다시 예시로 들어보자.
은행에서 대출 담당자가 신청자의 신상정보를 수집하여 검증한 후,
신청자의 신용도가 500보다 낮다면 대출 견적을 제공하지 않기로 결정했다.
따라서 시스템에서 신상정보 화면을 모두 채우고 검증한 후,
신용도가 하한성보다 높은지가 확인된 후에 대출 견적 화면으로 진행되어야 한다.
이것이 바로 유스케이스다.
유스케이스는 자동화된 시스템이 사용되는 방법을 설명한다.
유스케이스는
사용자가 제공해야 하는 입력,
사용자에게 보여줄 출력,
그리고 해당 출력을 생성하기 위한 처리 단계를 기술한다.
엔티티 내의 핵심 업무 규칙과는 반대로,
유스케이스는 애플리케이션에 특화된 업무 규칙을 설명한다.
위의 그림은 대출 애플리케이션의 유스케이스를 정리한 이미지이다.
위 유스케이스 내용에서 Customer를 언급하는 점에 주목하자.
Customer는 엔티티에 대한 참조이며,
은행과 고객과의 관계를 결정짓는 핵심 업무 규칙은 바로 이 Customer 엔티티에 포함된다.
유스케이스는 엔티티 내부의 핵심 업무 규칙을
어떻게, 그리고 언제 호출할지를 명시하는 규칙을 담는다.
주목할 또다른 점은,
인터페이스로 들어오는 데이터와 인터페이스에서 되돌려주는 데이터를
형식 없이 명시한다는 점만 빼면,
유스케이스는 사용자 인터페이스를 기술하지 않는 다는 점이다.
유스케이스 내용만 봐서는
이 애플리케이션이 웹을 통해 전달되는지, 콘솔 기반인지, 순수한 서비스인지를 구분하기란 불가능하다.
즉 유스케이스는 시스템이 사용자에게 어떻게 보이는지를 설명하지 않는다.
이보다는 애플리케이션에 특화된 규칙을 설명하며,
이를 통해 사용자와 엔티티 사이의 상호작용을 규정한다.
시스템에서 데이터가 들어오고 나가는 방식은 유스케이스와는 무관하다.
유스케이스는 객체다.
유스케이스는 애플리케이션에 특화된 업무 규칙을 구현하는 하나 이상의 함수를 제공한다.
또한 유스케이스는 입력 데이터, 출력 데이터, 유스케이스가 상호작용하는 엔티티에 대한 참조 데이터 등의
데이터 요소를 포함한다.
엔티티는 자신을 제어하는 유스케이스에 대해 아무것도 알지 못한다.
이는 의존성 역전 원칙을 준수하는 의존성 방향에 대한 또 다른 예시가 된다.
엔티티와 같은 고수준 개념은
유스케이스와 같은 저수준 개념에 대해 아무것도 알지 못한다.
반대로 저수준인 유스케이스는 고수준인 엔티티에 대해 알고 있다.
여기서 수준에 대한 개념이 또한번 등장하는데,
유스케이스는 단일 애플리케이션에 특화되어있고
해당 시스템의 입력과 출력에 보다 가까이 위치하기 때문에 저수준이라고 할 수 있다.
엔티티는 수많은 다양한 애플리케이션에 적용될 수 있도록 일반화된 개념이므로
각 시스템의 입력이나 출력에 더 멀리 떨어져있고 비교적 독립적이다. 따라서 엔티티는 고수준이다.
유스케이스는 엔티티에 의존하지만,
반면 엔티티는 유스케이스에 의존하지 않는다.
요청-응답 모델
유스케이스는 입력 데이터를 받아서 출력 데이터를 생성한다.
그런데 제대로 구성된 유스케이스 객체라면
데이터를 사용자나 또 다른 컴포넌트와 주고 받는 방식에 대해서는 전혀 눈치챌 수 없어야 한다.
우리는 유스케이스 클래스의 코드가 HTML이나 SQL에 대해 알게 되는 일을 절대 원치 않는다.
유스케이스는 단순 요청 데이터 구조를 입력으로 받아들이고,
단순한 응답 데이터 구조를 출력으로 반환한다.
이들 데이터 구조는 어떤 것에도 의존하지 않는다.
즉 그 어떤 사용자 인터페이스에도 종속죄지 않는다.
이처럼 의존성을 제거하는 일은 매우 중요하다.
요청 및 응답 모델이 만약 독립적이지 않다면
그 모델에 의존하는 유스케이스도 결국 해당 모델이 수반하는 의존성에 간접적으로 결합되어 버린다.
또한 엔티티와 요청/응답 모델은 상당히 많은 데이터를 공유하므로,
엔티티 참조를 요청/응답 모델 데이터 구조에 포함시키고 싶은 유혹을 받기 쉽다.
하지만 엔티티와 요청-응답 모델의 목적은 완전히 다르다는 내용을 기억하자.
시간이 지나면 두 객체는 완전히 다른 이유로 변경될 것이고,
따라서 두 객체를 어떤 식으로든 함께 묶는 행위는
공통 폐쇄 원칙과 단일 책임 원칙을 위배하게 된다.
결국 코드에는 수많은 떠돌이 데이터가 만들어지고,
수많은 조건문이 추가될 것이다.
결론
이처럼 업무 규칙은 소프트웨어 시스템이 존재하는 그 자체, 이유이다.
업무 규칙은 핵심적인 기능이고, 수익을 내며 비용을 줄이는 코드를 수반한다.
업무 규칙은 사용자 인터페이스나 데이터베이스와 같은 저수준의 관심사로 인해 오염되어서는 안되며,
원래 그대로의 모습으로 남아있어야 한다.
이상적으로는 업무 규칙을 표현한 코드는 반드시 시스템의 심장부에 위치해야 하며,
덜 중요한 저수준, 세부사항의 코드는 이 심장부에 플러그인 될 수 있는 구조여야 한다.
업무 규칙은 시스템에서 가장 독립적이며, 가장 많이 재사용할 수 있는 코드여야 한다.
#cleanArchitecture #클린아키텍처 #업무규칙 #유스케이스 #엔티티
[Architecture] 5부 아키텍처 : 22장 클린 아키텍처 (0) | 2024.01.17 |
---|---|
[Architecture] 5부 아키텍처 : 21장 소리치는 아키텍처 (1) | 2024.01.14 |
[Architecture] 5부 아키텍처 : 19장 정책과 수준 (0) | 2024.01.14 |
[Architecture] 5부 아키텍처 : 18장 경계 해부학 (1) | 2024.01.14 |
[Architecture] 5부 아키텍처 : 17장 경계 - 선 긋기 (1) | 2024.01.14 |
댓글 영역