[KSUG Seminar] Growing Application - 2nd. 애플리케이션 아키텍처와 객체지향 세미나를 듣고 정리한 내용입니다. 정리를 잘못해서 꼭 들어보면 좋을 것 같아요😊
서비스별로 레이어가 세분화 되어 여러가지 레이어가 존재할 수 있다. 서비스에 대한 정보를 많이 담고 있는 비즈니스 로직이 담겨 있는 도메인 레이어가 가장 중요하다. 따라서 도메인 레이어가 각 레이어들에 영향을 미치게된다.
그러므로 서비스에 있어서 도메인을 어떻게 코드로 잘 반영하는가가 중요하다.
도메인 레이어 설계 방법에는 크게 2가지가 있다.
⇒ 즉, 도메인 레이어를 캡슐화하여 다른 로직이 침투할 수 없도록 해야한다. 이를 위해 레이어를 하나 추가하고 서비스 레이어라고 한다.
async function order({customerId, productId, count}: {customerId:number, productId:number, count:number){
// 필요한 데이터를 가져온다. (도메인 로직을 위한 준비)
const customer = await this.customerRepository.findByIdOrFail(customerId);
const product = await this.productRepository.findByIdOrFail(productId);
// 도메인 객체에 필요한 행위를 위임한다.
const order = customer.order(product, count);
// 결과를 받아 저장한다. (도메인 로직에 대한 후처리)
this.orderRepository.save(order);
return order;
}
예시)
async function order({customerId, productId, count, point}: {customerId:number, productId:number, count:number, point: number){
// 필요한 데이터를 가져온다. (도메인 로직을 위한 준비)
const customer = await this.customerRepository.findByIdOrFail(customerId);
const product = await this.productRepository.findByIdOrFail(productId);
const rules = await this.productRuleRepository.findByProductId(productId);
// 필요한 프로세스 진행
if(!customer.hasPoint(point)){
throw new ApiError(httpStatus.BAD_REQUEST, ErrorInfo.NOT_ENOUGH_POINT);
}
if(!product.isOrderable()){
throw new ApiError(httpStatus.BAD_REQUEST, ErrorInfo.INVALID_PRODUCT_STATUS);
}
const discountPrice = rules.reduce((prev,cur)=>{
if(cur.isValidRule()){
return prev + cur.getDiscountPrice(product);
}
return prev;
},0)
const order = new Order();
order.userId = customer.id;
order.productId = product.id;
order.price = (product.getPrice() * count) - discountPrice;
// 저장
this.orderRepository.save(order);
return order;
}
예시)
async function order({customerId, productId, count}: {customerId:number, productId:number, count:number){
// 필요한 데이터를 가져온다. (도메인 로직을 위한 준비)
const customer = await this.customerRepository.findByIdOrFail(customerId);
const product = await this.productRepository.findByIdOrFail(productId);
// 도메인 객체에 필요한 행위를 위임한다.
const order = customer.order(product, count);
// 결과를 받아 저장한다. (도메인 로직에 대한 후처리)
this.orderRepository.save(order);
return order;
}
대부분의 초기 개발 시, 요구사항은 계속 변경되고 어떻게 변경될지 알 수 없다. 객체지향으로 작성했다고해서 요구사항을 쉽게 반영할 수 있는 경우는 거의 없다. 항상 틀어지게 된다. 그러므로 최대한 단순하게 코드를 짜야하며 변경될 때마다 계속해서 리팩터링이 이루어져야 한다.
계속해서 변경해가다가 어떤식으로 변경이 되겠구나에 대한 판단이 서면 객체지향적인 코드가 많이 도움을 줄 수 있다.
지금까지 controller, service를 나눠야 한다는 생각만 가지고 나눠왔지만, 강의를 듣고나니 왜 controller와 service를 나눠야 하는지 생각해보게 되었다.
이 주된 이유라고 생각한다.
회사 서비스 당시, 회사코드는 controller와 service로 폴더 구조가 나누어져있었다. 위 강의 내용에 따르면, 트랜잭션 스크립트 방식으로 짜여져있었고 작성했던 service들은 서비스레이어가 아니라 본질적으로 도메인 레이어라고 보는게 맞는것 같다.
그리고 당시에는 data source레이어, 즉 repositroy나 dao를 따로 가지고 있지 않았다. 재사용을 위해 service내부에 데이터를 불러오는 메서드, 데이터의 상태를 확인하는 메서드 등을 만들게 되었다. 그로 인해, 서비스 폴더가 점점 커져갔고 관리가 힘들다고 느꼈다. 그치만 다른 무언가를 추가해 나눠야할지, 추가한다면 어떤 구조를 추가해야할지 도저히 감이 잡히지 않았고, 추가하지 않았었다.
이 후, nest와 orm을 사용하는 방향으로 전환했기에 이런문제들은 어느정도 해결되었지만, 생각해보면 nest로 전환하는게 아닌 단순히 data source레이어를 추가하고, 가져온 데이터를 맵핑하여 사용했더라면, 더 실용적이지 않았을까 라는 생각도 들었다.