[F-Lab 모각코 챌린지 29일차] DDD

부추·2023년 6월 29일
0

F-Lab 모각코 챌린지

목록 보기
29/66

TIL

  1. 멘토링 회고 : package 구조
  2. 도메인 주도 개발 시작하기
  3. 코테 구현 한문제



1. 멘토링 회고

어제도 어찌저찌 나름대로 잘 방어한 멘토링 시간이었다 ^^.(멘토님 입장에선 아닐 확률 94.125%) SOLID에 대해서 얘기 나눴는데, 기억 나는것 + 몰랐던 것만 간단히 정리!

책임을 나눴을 때 이점

SRP는 "responsibility"를 나누는 과정이다. 클래스마다 하는 일이 나눠져있으면 하나의 일을 하는 코드가 한 클래스에 몰려있게되고, 응집력이 높아셔 특정 기능의 변경이 다른 기능에까지 영향을 끼치는 것을 막을 수 있다.

추가로 재사용성을 높일 수 있는데, 이는 한 코드에 기능이 모여있으니 특정 기능을 사용할 때 가볍고 편하게 해당 모듈을 불러올 수 있기 때문이다. SRP를 위반한 클래스 코드는 내부에 여러 기능이 복잡하게 얽혀있기 때문에, 특정 기능을 사용하고 싶을 때 클래스 내의 부분을 떼어내 사용하기 힘들다.

LSP와 계약에 의한 설계

  • 사전 조건 : 메서드가 실행되기 전 보장되어야 할 조건
  • 사후 조건 : 메서드의 실행 이후 메서드가 보장해야 할 조건

하위 클래스에서 사전 조건이 강해져선 안되며, 사후 조건이 약해져서도 안된다. 클라이언트 코드가 상위 클래스의 메소드를 수행했을 때 기대하는 모든 것을 하위 클래스에서 수행할 수 있어야 진정한 다형성을 구현했다고 할 수 있기 때문이다.

멘토님께서 이에 대한 예시를 들어보라 했을 때 어벙~ 했는데 좋은 예시를 하나 알아갔다. 전체 정수값을 인자로서 받는 상위 클래스 메소드를 오버라이딩한 하위 클래스 메소드가 인자로 양수만을 허용하는 예시다. 사전 조건이 정수에서 양수로 강해졌다. 이는 LSP 위반이다!

고수준 컴포넌트란?

DIP는 고수준 컴포넌트가 저수준 컴포넌트에 의존하면 안된다는 원칙이다. 자주 바뀌는 구체적 기술인 저수준 컴포넌트에 의해 고수준 컴포넌트가 영향을 쉽게 받으면 변경에 취약한 코드가 되기 때문이다. 그럼 여기서 고수준 컴포넌트란 정확히 뭘까?! 사실 지금도 몰라~~!

일단 상대적으로 접근하는게 베스트긴 한데, 고수준 컴포넌트는 정책적인 것이라고 한다? 도메인에 대한 제약사항 말이다. 뭐 배송 상태를 바꿀 수 있는 것은 상품 출고 이전이라든가, 상품 가격으로 음수가 오면 안된다든가.. 슬랙을 예시로 들면 슬랙 메세지 속 스레드는 반응이 추가되면 삭제할 수 없다든가.. 그런 데이터 제약사항을 표현해 놓은 것을 일반적으로 "고수준 컴포넌트"로 정의하는듯하다.

DIP를 위반하면? 기술이 바뀌었다고 이런 도메인 정책까지 바뀌어야 하는 상황이 올 수도 있다. 기술에 따라 달라지는 정책이라니 뭔가 힙하다 ㅋㅋ 아무튼 이를 피하기위해 DIP에서는 OCP와 같은 방법으로, 고수준 컴포넌트가 의존하면서 저수준 컴포넌트가 구현할 수 있는 상위 인터페이스를 하나 둔다.




2. 도메인 주도 개발 시작하기

오랜만의 독서!!! 는 아니고, (그 전에 오브젝트와 HTTP 완벽 가이드를 주구장창 읽었기 때문) 진짜 코드와 관련된 독서라 신난다!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

2-1) 도메인 모델 시작하기

  1. 도메인 : 소프트웨어로 해결하고자 하는 문제 영역. 커머스 시스템의 경우, 배달 / 결제 / 정산 / 주문 / 회원 / 혜택 / 리뷰 등이 그 대상이 된다. 각 도메인엔 해당 영역에 특화된 전문가가 존재하고, 결제나 배송같은 경우 외부 시스템을 이용하기도 한다.
  2. 도메인 전문가 : 각 도메인 영역엔 전문가들이 존재한다. 혹은 그럴 확률이 높다. 그들의 요구사항을 잘 이해하기 위해 코딩 전 깊이 대화하는 것이 중요!
  3. 도메인 모델 : 객체 기반 다이어그램, 상태 기반 다이어그램 등 도메인을 이해하기 위한 도메인 모델은 이해하기 쉽다면 어떤 형태를 띄든 상관없다. 또한 하위 도메인마다 용어 의미가 다를 수 있으므로 이를 확실히 해야한다.(상품목록의 가격과 주문목록의 가격은 다른 의미!)
  4. 도메인 모델 패턴 : 도메인 계층은 핵심 규칙(ex. 배송 상태가 출고 이후라면 주문 취소 X)이 정의된 코드가 있다. "핵심 규칙" "로직"은 도메인 계층에 들어있다!
    • Presentation : 사용자의 요청 처리, 사용자에게 결과를 반환
    • Application : 기능 수행. 로직이 직접 들어있지 않고 domain에서 제공하는 기능을 조합
    • Domain : 시스템이 제공하는 도메인 규칙을 구현
    • Infrastructure : DB, 메세징 시스템같은 외부 연동 시스템 처리
  5. 도메인 모델 도출 : 요구사항을 이용해서 각 객체들에 필요한 필드와 verify 메소드들을 구성한다.
  6. Entity와 Value
    • Entity는 바뀌지 않는 "식별자"를 가짐으로써 고유한 객체가 된다. 식별자(ID)는 특정 규칙을 따라 생성하거나, UUID같은 생성기를 이용하거나, MySQL 등의 DB에서 제공하는 식별자를 사용하기도 한다.
    • Value는 "개념적으로 완벽한 하나"를 표현할 때 사용한다. 주문정보에서 이용되는 사람, 주소 객체 등이 그 예시이다. 불변 타입으로 사용되는 것이 선호된다.
    • 돈을 표현하기 위해 Money클래스 타입, ID를 표시하기 위한 UserId 클래스 타입을 만드는 식으로 "의미를 명확하기 위해" Value 타입을 사용하기도 한다. 저장값의 의미를 명확하게 하기 위함이다.
    • 도메인 모델에 setter를 지양하자. 도메인 지식을 코드에 담아내기 힘들기 때문이다. 또한 도메인 객체 생성때 완전한 객체(필드에 null X)를 만드는데 방해가 된다.
  7. 도메인 용어 : 코드의 가독성을 높이고 이해에 걸리는 시간을 줄이기 위해 코드엔 되도록 도메인 용어를 사용하자. 코딩 시에 a,b,c 등의 변수값을 자제하는 이유와 같다. 이는 도메인 전문가들과의 협업 역시 원활하게 해준다.

2-2) 아키텍처 개요

  1. 네 개의 영역
    • 표현 영역 : 사용자의 요청을 응용 영역에서 사용하는 포맷으로 전환해 전달하고, 응용 영역의 결과를 다시 사용자에게 보여주는 역할. HTTP 메세지를 파싱하거나, JSON 응답 객체를 내려주는 등의 일 수행
    • 응용 영역 : 시스템이 제공하는 기능을 구현. 도메인 모델의 있는 로직을 이용
    • 도메인 영역 : 도메인 모델 구현. 도메인 모델과 관련한 제약사항과 로직 등이 존재
    • 인프라스트럭처 영역 : 구현 기술에 대한 것.
  2. 계층 구조 아키텍처 : 표현 -> 응용 -> 도메인 -> 인프라스트럭처 방향으로 의존성이 생기는데, 필요할 때 응용 영역에서 바로 인프라스트럭처로 의존성이 생기기도 한다. 그러나 저수준 모듈인 인프라스트럭처에 직접 의존할 경우, 테스트와 기능 확장의 어려움이 있을 수 있다.
  3. DIP : 인프라스트럭처의 상위 추상 인터페이스를 둬서 2번 문제를 해결하는 법. 이때 추상 인터페이스는 고수준 모듈이므로 도메인 영역에 존재한다.
  4. 도메인 영역의 주요 구성요소
    • 엔티티 : 고유 식별자를 갖는 객체로, 도메인 고유한 데이터와 함께 기능을 제공
    • 밸류 : 개념적으로 하나인 값을 표현할 때 사용. Money, Address 등. 밸류는 불변일 것을 권장(정책 로직을 수행시키기 위함)
    • 애그리거트 : 연관된 엔티티와 밸류 객체를 개념적으로 묶은 군집으로, 전체 도메인 구조를 이해하고 캡슐화 단위로써 좋다.
    • 리포지터리 : 도메인 모델의 영속성을 처리. 실제 "구현"을 위한 도메인 모델로, DB에서 엔티티 객체를 조회하는 등의 역할 수행
    • 도메인 서비스 : 특정 엔티티에 속하지 않는 도메인 로직 제공. 커머스에서 상품 할인을 계산할 때 여러 애그리거트를 이용하게 되는데, 이때 도메인 서비스에서 이를 구현
  5. 요청 처리 흐름 : 사용자의 HTTP 요청 -> 표현 계층의 변환 -> 응용 영역이 리포지토리/도메인 기능을 이용하여 로직 수행 -> 표현 계층에 반환 -> 리턴
  6. 인프라스트럭처 개요 : 도메인, 혹은 응용 영역에서 DIP를 해치지 않는 범위 내에서 인프라스트럭처에 대해 의존할 수도 있다.(ex. @Transactional, @Entity, @Table 등)
  7. 모듈 구성 : 4개 영역은 별도 패키지에 위치. 프로젝트의 도메인이 크면 애그리거트 단위로 도메인을 나느고 그 안에 4개의 영역을 다시 분리한다. 각 도메인은 서로 협력한다.




3. 이모티콘 할인 행사

단순 구현 문제! 근데 itertools의 중복순열을 이용한.

from itertools import product
def solution(users, emoticons):
    n,m = len(users),len(emoticons)
    ans_reg,ans_amount = -1,-1

    sales = product((10,20,30,40),repeat=m)
    for sale in sales:
        cur_reg = 0 # 지금 이 할인율에서의 가입자
        user_purchase_amount = [0 for _ in range(n)] # 각 유저의 구매 금액

        for i in range(m):
            price = emoticons[i] * (1-(sale[i]*0.01))
            for j in range(n):
                if (users[j][0]<=sale[i]):
                    user_purchase_amount[j] += price
        
        for i in range(n):
            if (user_purchase_amount[i] >= users[i][1]):
                user_purchase_amount[i] = 0
                cur_reg += 1
        
        if (cur_reg<ans_reg):
            continue
        elif (cur_reg>ans_reg):
            ans_reg = cur_reg
            ans_amount = sum(user_purchase_amount)
        else:
            ans_amount = max(ans_amount,sum(user_purchase_amount))

    return [ans_reg,int(ans_amount)]

할인율의 경우가 4개, 이모티콘이 최대 7개라는거 보고 딱 브루트포스 구현 문제인 것에 감이 왔다. 4개의 할인율 각각에 대해 모든 이모티콘의 값을 중복순열로 구했다. 존재하는 모든 경우를 체크한거지? 그때마다 사용자들의 구매 여부 및 이모티콘 플러스 가입 여부를 체크했다. user_purchase_amount의 각 인덱스에는 사용자들의 구매 금액이 담겨있다. 특정 유저가 이모티콘 플러스에 가입할 경우 해당 인덱스 값은 0, cur_reg값을 더해 가입자 수가 늘어났음을 표시했다!

나머지는 간단. 가입자수가 현재 최선 이상일 경우 이모티콘 판매 액수인 ans_amount를 업데이트한다. 모든 경우를 돌아본 뒤 나온 최선의 답을 리턴!



REFERENCE

https://armadillo-dev.github.io/design/programming/desing-by-contract/

profile
부추튀김인지 부추전일지 모를 정도로 빠싹한 부추전을 먹을래

0개의 댓글