Skip to content

Commit

Permalink
CartProbe 샘플 코드 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
jojoldu committed Mar 12, 2024
1 parent a617c2a commit 6d9ce05
Showing 1 changed file with 50 additions and 23 deletions.
73 changes: 50 additions & 23 deletions posts/logging/loggin_is_feature/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 로깅과 도메인
# 로깅도 도메인처럼

시스템을 구축하다보면 다음과 같이 크게 2개의 로그를 남긴다.

Expand All @@ -14,54 +14,81 @@ logger.trace(`Total Order Price Amount: ${sum(products.amount)}`);
이 2개의 로그 모두 로깅을 표현하지만, 실상은 목적이 다르다.

- 에러 (정보) 로깅은 운영 환경에서 장애를 디버깅하거나 실행 중인 시스템의 진행 상황을 모니터링하기 위해 추적하기 위한 것이다.
- 디버깅 로깅은 프로그래머가 개발 중인 시스템 내부에서 어떤 일이 일어나고 있는지 이해하는 데 도움을 주기 위한 것이므로 프로덕션 환경에서는 실행되지 말아야 한다.
- 디버깅 로깅은 프로그래머가 개발 중인 시스템 내부에서 어떤 일이 일어나고 있는지 이해하는 데 도움을 주기 위한 것으로 프로덕션 환경에서는 실행되지 말아야 한다.

이러한 차이점을 고려할 때 이 **두 가지 유형의 로깅에 서로 다른 방법을 사용하는 것을 고려**해야 한다.

에러 로깅은 감사 로그나 장애 복구와 같은 누군가의 요구 사항에 따라 테스트 중심으로 이루어져야 한다.
또한 목적이 명확하니 메시지 역시 일관적이어야 한다.
에러 로깅은 감사 로그나 장애 복구와 같은 누군가의 요구 사항에 따라 정상적으로 작동하는지 검증이 필요하다.
운영 환경에서 올바르게 작동하는 것을 보장하기 위해 **테스트 코드 역시 작성이 필요하다**.

또한 목적에 맞게 메시지 역시 일관적이어야 한다.

반면에 디버깅 로깅은 **운영 환경에서의 올바른 작동이 필요하지 않다**.
오로지 프로그래머가 개발 단계에서 시스템 내부의 작동 방식을 확인하기 위한 용도이므로 **테스트를 거칠 필요가 없으며 에러 로그처럼 메시지가 일관적일 필요도 없다**.

똑같은 로그인데 굳이 이 2개를 구분해서 관리가 필요할까? 라는 생각이 든다면, **에러 (정보) 로그는 로그 보다는 지표 전송**으로 생각해보자.
로그를 남기는게 아니라 지표전송 혹은 알람으로 보내야 한다고 생각하면 좀 더 해당 로그를 도메인처럼 생각할 수 있다.
로그를 남기는게 아니라 지표 전송 혹은 알람으로 보내야 한다고 생각하면 좀 더 해당 로그를 도메인처럼 생각할 수 있다.

예를 들어 다음과 같이 운영 환경에서의 상황을 모니터링 하기 위해 다음과 같이 "카트에서 음식 상품을 삭제 처리할때만 추적"이 필요하다고 해보자.

```ts
function removeCart(product: Product) {
httpClient.removeProduct(product.id);
if(product.type === ProductType.FOOD) {
logger.info(`Removed Cart ${product.id}, ${product.name}, ${product.price}`);
export class CartService {
...
removeCart(product: Product) {
httpClient.removeProduct(product.id);
if (product.type === ProductType.FOOD) {
logger.info(`Removed Cart ${product.id}, ${product.name}, ${product.price}`);
}
}
}
```

위 코드에서는 카트 속 상품을 지우는 기능과 더불어서 로깅 인프라가 함께 포함되어 있다.
위 코드에서는 **카트 속 상품을 지우는 기능과 더불어서 로깅 인프라가 함께 포함**되어 있다.
이 코드에서 주요 코드는

1) `httpClient.removeProduct(product.id);` 를 호출하는 것
2) `FOOD` 타입인 경우 `info` 로그를 남기는 것

인데 실제 **코드의 대부분은 로그를 남기는 것**이다.
이 함수가 과연 로그를 남기는 것을 목적으로 하는 함수였을까?
그렇지 않다.
다만, 에러 (정보) 로그를 로깅 인프라로 바라보고 사용하다보니 주요 로직보다 로깅 코드가 훨씬 더 많은 함수가 되었다.

또한, 테스트 구현이 필수적인데, 테스트 코드 작성도 어렵다.

이 에러 (정보) 로깅은 **운영 환경에서 꼭 정상 작동해야하는 기능**이다.
즉, **테스트 코드로 꼭 검증이 필요한 영역**이다.

하지만, Java와 같이 Logger를 Static 변수로 사용하는 분야 혹은 그와 유사한 환경에서는 테스트로 기능 검증이 어렵다.
함수 안에서 logger로 출력 되는 부분만 단독으로 검증하기 위해서는 Logger를 DI로 받을 수 있는 객체로 변환해야만 한다.

하지만, Java와 같은 환경에서는 `static logger` 는 테스트 검증이 어렵다.
또한
이 코드는 위치와 렌더링 에러 정보라는 두 가지 작업을 동시에 수행하므로 단일 책임 원칙을 위반한다.
그래서 이러한 문제들을 해결하기 위해서 위 **로깅 정보를 별도의 관측 객체에 위임**하는 코드로 개선해본다.

테스트의 노이즈는 코드가 도메인과 로깅 인프라의 두 가지 수준에서 작동하고 있음을 알려준다.
므로 테스트로 기능 검증이 필수적**이다.
```ts
export class CartService {

루프의 기능적인 부분과 로깅 부분 사이의 어휘와 스타일이 달라진 것을 주목해보자.
constructor(cartProbe: CartProbe) {
this.cartProbe = cartProbe;
}

removeCart(product: Product) {
httpClient.removeProduct(product.id);
this.cartProbe.remove(product); // Cart 관측 객체에 위임
}
}

```ts
function removeCart(product: Product) {
httpClient.removeProduct(product.id);
cartProbe.remove();
export class CartProbe {
remove(product: Product) {
if (product.type === ProductType.FOOD) {
logger.info(`Removed Cart ${product.id}, ${product.name}, ${product.price}`);
}
}
}
```

에러 객체가 로거, 메시지 버스, 팝업 창 등으로 구현될 수 있으며, 이 세부 사항은 이 수준의 코드와 관련이 없다.

이 코드는 테스트하기도 더 쉽다.
이 코드는 테스트하기도 더 쉽다.

로깅 프레임워크가 아닌 우리가 에러 객체를 소유하고 있으므로 편의에 따라 모의 구현을 전달하고 테스트 케이스에 로컬로 유지할 수 있다.
또 다른 단순화는 이제 형식이 지정된 문자열의 내용이 아닌 객체에 대해 테스트한다는 것이다.
물론 여전히 에러 구현과 이에 초점을 맞춘 몇 가지 통합 테스트를 작성해야 한다.
Expand All @@ -71,7 +98,7 @@ function removeCart(product: Product) {
**구현 (로깅)이 아니라 의도 (에러 담당자를 돕는 것)에 따라 코드를 작성한다는 의미이므로 더 표현력이 풍부**해진다.
모든 에러 보고는 알려진 몇 군데에서 처리되므로 보고 방식에 일관성을 유지하고 재사용을 장려하기가 더 쉬워진다.

또한 Java 패키지가 아닌 애플리케이션 도메인의 관점에서 보고를 구조화하고 제어하는 데 도움이 될 수 있다.
또한 애플리케이션 계층으로 패키지 (디렉토리)를 바라보는 것이 아닌 애플리케이션 도메인의 관점에서 보고를 구조화하고 제어하는 데 도움이 될 수 있다.

마지막으로, 각 보고서에 대해 테스트를 작성하는 행위는 **애매한 오류 조건을 처리하지 않아 로그가 늘어나고 프로덕션 장애로 이어지는 "이 예외를 어떻게 처리해야 할지 모르겠으니 일단 로그하고 계속 진행하겠다"는 증후군을 방지하는 데 도움이 된다**.

Expand Down

0 comments on commit 6d9ce05

Please sign in to comment.