React 컴포넌트를 작성하던 도중, 예전에 애매모호하게 이해하고 넘어간 개념이 문득 떠올랐다.
설명을 들어가기 전, 하나의 간단한 생성자를 구현해보자.
지금부터 구현할 생성자는, 개발자 instance를 생성해내는 생성자이다!
type Positions = "FE" | "BE" | "DevOps"; class Dev { #position; constructor(position: Positions = "FE") { this.#position = position; } } // 아무 값을 넣지 않을 경우, 펭구스와 같은 FE 개발자를 기본값으로 설정한다.
위의 Dev 생성자로 새로운 개발자 인스턴스를 생성해보자.
const pengooseDev = new Dev("FE");
쨘! 귀여운 펭귄 개발자(FE)가 탄생했다.
안귀엽다고? 유감..
Pengoose는 FE개발을 하다, BE에 새로운 흥미가 생겼다.
따라서, BE로 포지션을 변경하고자 한다.
Pengoose가 가진 속성(state)를 변경하는 메서드(로직)를 비즈니스 로직이라고 한다.
비즈니스 로직을 간단히 표현하자면 state를 CRUD하는 메서드이다!
말 그대로 state를 변경하는 일련의 메서드나 로직을 모두 비즈니스 로직이라 말한다.
Pengoose의 position을 변경하는 비즈니스 로직을 구현해보자.
class Dev { //.. 이전 코드들 // position이라는 state를 수정하는 비즈니스 로직! // Dev라는 객체가 가지고있는 메서드로 우리가 직접 구현(추상화)한 것이다! changePosition (newPosition: Positions) { this.#position = newPosition; } }
자, 이제 BE 개발자로 2차 전직 들어가보자.
pengooseDev.changePosition("BE");
쨘! Pengoose는 position을 변경하는 비즈니스 로직을 거쳐 BE 개발자가 되었다!
눈치 챘는지 모르겠지만, 눈물점 하나 추가했다.
(예전 어떤 드라마에선 눈물점 하나 찍으니까 못알아보더라)
따라서 현재 코드는 다음과 같다.
type Positions = "FE" | "BE" | "DevOps"; class Dev { #position; constructor(position: Positions = "FE") { this.#position = position; } changePosition (newPosition: Positions) { this.#position = newPosition; } }
state와 이를 CRUD하는 메서드를 하나의 객체로 캡슐화 해둔 것.
우리는 객체를 객체스럽게 사용하기 위해, 관심사(도메인)가 동일한 state와 메서드들을 하나의 객체로 묶어(캡슐화) 관리한다. 이렇게하면 유지보수나 확장성에서 큰 강점을 얻을 수 있기 때문이다.
즉, 객체를 객체답게 사용하기 위해 공통의 관심사(도메일)끼리 캡슐화 해 둔 것이다.
이해가 어렵다고?
코드의 재사용성과 확장성, 유지보수에 관심을 가지고 코드 리팩터링을 깊게 고민해보지 않았다면 조금은 와닿지 않는 내용일 수 있다.
객체는 객체답게..? 뭔 추상적인 이야기냐..?
객체는 객체답게라...
아래의 예시를 살펴보자.
우리의 Pengoose. 배가 고파졌다.
Pengoose는 저녁밥을 해먹기 위해 침대에서 일어난다.
문제 1) Pengoose의 다음 행선지로 적절한 것을 고르시오.
1) 화장실
2) 침대
3) 부엌
4) 피팅룸
5) 피트니스룸
뭐 당연히 답은 3번이다.
왜 부엌으로 가는가?
"부엌"이란 공간(도메인)의 목적(관심사)은 "음식(state)을 보관하고 조리(비즈니스 로직)"하는 것이기 때문이다.
코드를 작성한다는 것은, 우리에게 필요한 state와 비즈니스 로직을 추상화하며, 우리만의 세상을 만들어 나아가는 것이다.
일단 장봐온 양배추(state)와 계란(state)을 냉장고에서 넣는다(비즈니스 로직).
class 냉장고 { this.#양배추; // state this.#계란; // state constructor() { this.#양배추 = 0; this.#계란 = 0; } 양배추_추가(amount: number) { // 비즈니스 로직 this.#양배추 += amount; } 계란_추가(amount: number) { // 비즈니스 로직 this.#계란 += amount; } } const 펭구스의_냉장고 = new 냉장고(); const 구매한_계란의_수 = 30; const 구매한_양배추의_수 = 1; 펭구스의_냉장고.계란_추가(구매한_계란의_수); 펭구스의_냉장고.양배추_추가(구매한_양배추의_수);
위에서 계속 언급했지만 예시를 봐서 알겠지만, 비즈니스 로직은 state의 변경하는 것이기 때문에, 비즈니스 로직이 발생하면 state가 대부분 변화한다.
양배추(state)를 꺼내서(비즈니스 로직) 채를 썬 뒤(비즈니스 로직) 가스레인지의 불(state)을 켜고(비즈니스 로직) 후라이팬(state)을 식기세척기에서 가스레인지로 옮기고(비즈니스 로직) 기름을 두른 뒤(비즈니스 로직) 열이 오르는 동안, 계란(state)을 냉장고에서 꺼내(비즈니스 로직) ...
이해했을 거라 믿어 의심치 않는다.
당신은 이미 객체를 객체스럽게 사용하는 세상 안에 살고있다.
주유소에는 자동차와 관련된 state와 비즈니스 로직을 제공하고있고, 병원에선 질병 및 환자에 관련된 state와 비즈니스 로직이 존재한다.
이미 위에 어느정도 몰래몰래 잘 설명 했었으니, 이미 이해했겠지만.
짧게 요약하자면
관심사에 따라 분리한 "공간"이다.
보통 도메인과 관심사가 비슷하게 이야기되는 이유는, 관심사가 같은 것끼리 같은 공간(객체)에 분류하기 때문이다. 하지만, 엄밀히 말해 관심사와 도메인이 같은 말은 아니다. (위쪽에서는 이해하기 편하게 괄호로 표기했었다)
하나의 캡슐화된 객체는 하나의 도메인이지만, 하나의 도메인 내부에도 또 각자의 도메인으로 나뉘어질 수 있으며, 캡슐화된 객체가 모여 하나의 도메인이 될 수 있다.
class BankService {
constructor() {
this.accounts = [];
}
createAccount(initialBalance) {
const account = new Account(initialBalance);
this.accounts.push(account);
return account;
}
transferFunds(sourceAccount, destinationAccount, amount) {
sourceAccount.debit(amount);
destinationAccount.credit(amount);
}
}
얼핏보면 Model과 비슷해보인다.
하지만 이 한마디로 정리할 수 있겠다.
Service Layer는 Model이란 레고를 조립해 만드는 하나의 유기체이다.
Model은 캡슐화되어, 각자의 책임과 state. 그리고 이를 핸들링하는 method들로 구성된다. 즉! MVC 패턴을 통해 간단한 프로그램을 작성해봤다면, Controller로 이해해도 좋다. 각 객체들이 하나의 객체에 레고처럼 조립되어, 하나의 유기체를 완성하는 것이다.
// Model
class Account {
constructor(balance) {
this.balance = balance;
}
debit(amount) {
this.balance -= amount;
}
credit(amount) {
this.balance += amount;
}
}
// Service Layer
class BankService {
constructor() {
this.accounts = [];
}
createAccount(initialBalance) {
const account = new Account(initialBalance);
this.accounts.push(account);
return account;
}
transferFunds(sourceAccount, destinationAccount, amount) {
sourceAccount.debit(amount);
destinationAccount.credit(amount);
}
}
그러하다.