Code Monkey home page Code Monkey logo

anam-earth-be-fastapi's Introduction

My Github Stats

anam-earth-be-fastapi's People

Contributors

jseop-lim avatar

Watchers

 avatar

anam-earth-be-fastapi's Issues

[질문] 엔티티 참조 필드의 타입

Node.edges의 타입

list[Node] vs. set[Node]

Node.edges 원소를 추가할 때 다른 노드에 대한 중복성 검사는 수행하므로 유일성은 보장된다.

  • list: 두 노드를 비교할 때 edges의 순서가 영향을 끼치므로 __eq__()를 정의하고 edges는 비교 대상에서 제외해주어야 한다.
  • set: Edge.nodes가 hashable하다면, 두 노드의 같음을 비교할 때 비교 대상에 edges를 포함해도 문제 없다. frozen=False 이고 unsafe=False인 dataclass 객체는 hashable하지 않다. 따라서 Edge.nodes의 타입을 set으로 쓰려면 unsafe_hash 옵션을 키거나 __hash__() 함수를 정의해야 한다.

Edge.nodes의 타입

tuple[Node, Node] vs. set[Node]

Edge.nodes 초기화할 때 원소 중복성 검사는 수행하므로 두 원소의 상이함은 보장된다.

  • tuple: 원소 개수가 보장된다. 두 간선을 비교할 때 tuple 원소 간 순서가 영향을 끼치므로 __eq__()를 오버라이딩해야 한다.
  • set: 두 Edge를 비교할 때 nodes 원소 간 순서를 고려할 필요가 없다. 중복 검사를 따로 수행할 필요가 없다. 다만, 원소 개수에 대한 검사는 따로 추가해야 한다. Node.edges의 타입을 set으로 쓰려면 unsafe_hash 옵션을 키거나 __hash__() 함수를 정의해야 한다.

[질문] 노드 엔티티 업데이트 유스케이스 구현

  1. 새로운 Node 엔티티 객체 생성하고 새로운 엔티티를 영속하게 저장
class UpdateNodeUseCase(PartialUpdateNodeInputBoundary):
    def __init__(self, node_repo: NodeRepository) -> None:
        self.node_repo = node_repo

    def execute(self, input_data: PartialUpdateNodeInputData) -> None:
        old_node: Node = self.node_repo.get_node_by_id(node_id=input_data.id)
        new_node = Node(
            id=input_data.id,
            name=input_data.name or old_node.name,
            point=Point(
                longitude=input_data.longitude or old_node.point.longitude,
                latitude=input_data.latitude or old_node.point.latitude,
            ),
        )
        if old_node != new_node:
            self.node_repo.update_node(node=new_node)
  1. 기존 Node 엔티티의 속성을 메서드로 조작하고 기존 엔티티를 영속하게 저장
class UpdateNodeUseCase(UpdateNodeInputBoundary):
    def __init__(self, node_repo: NodeRepository) -> None:
        self.node_repo = node_repo

    def execute(self, input_data: UpdateNodeInputData) -> None:
        node: Node = self.node_repo.get_node_by_id(node_id=input_data.id)
        if input_data.name:
            node.update_name(name=input_data.name)
        if input_data.longitude or input_data.latitude:
            node.update_point(
                point=Point(
                    longitude=input_data.longitude or node.point.longitude,
                    latitude=input_data.latitude or node.point.latitude,
                ),
            )
        self.node_repo.update_node(node=node)

[질문] 노드와 연관된 간선 삭제 명령은 어디에서 이뤄지나?

infra

노드 삭제 레포지토리 구현체에서 cascade 속성을 미리 설정해놓고 실행.
혹은 해당 노드 행을 외래키로 참조하는 간선 테이블의 행을 함께 삭제하는 동작 실행

use case

레포지토리를 통해 DB에서 해당 노드 엔티티를 참조하는 간선 엔티티 객체들을 반환
레포지토리를 통해 DB에서 해당 간선 엔티티에 대응하는 간선 엔티티의 행을 삭제

[질문] 간선 엔티티가 노드를 보유하는 형태와 간선 레포지토리

  1. 간선 엔티티는 속성으로 노드 엔티티를 가진다. 간선 레포지토리가 간선 엔티티를 반환할 때는 노드의 속성도 모두 함께 DB에서 가져온다.
  2. 간선 엔티티는 속성으로 노드 ID를 가진다. 간선 레포지토리가 간선 엔티티를 반환할 때는 노드의 ID만 DB에서 가져온다. 노드의 속성이 궁금하다면 노드 레포지토리를 사용한다.

[TODO] 프로젝트 목표

주요 목표

  • FastAPI + SQLArchemy 연습하기
  • 클린 아키텍처 실습하기, DDD 일부 적용하기

부가 목표

  • Git 부가기능 활용하기

    • .gitmessage
    • git-hook: pre-commit, commit-msg
    • git subtree / git submodule
  • 파이썬 정적 도구 활용하기

    • isort
    • flake8
    • black formatter
    • 타입 검사기 mypy
  • 파이썬 관련 도구 활용하기

    • poetry 실사용 해보기
    • pytest coverage
  • 프로그래밍 기타

    • 의존성 주입 프레임워크 도입하기
    • TDD 적용해보기
    • API Versoning
  • 인프라 신기술 연습

    • Docker
    • Amazon ECS
    • Amazon VPC
    • Amazon Code Deploy
    • terraform
    • Amazon S3 프론트 배포
    • 로그 쌓고 분석
    • Amazon CloudWatch 연동

[질문] 간선 목록 조회 레포지토리 구현

배경 지식

관련 이슈: #29

간선과 노드는 하나의 aggregate에 속한다. aggregate root는 노드이다. 따라서 use case에서는 간선 엔티티를 import하여 객체를 생성하고 접근할 수 없다.

선택 사항

노드 레포지토리에 간선 목록 조회 메서드를 추가할 것인지 결정한다. 간선 목록 유스케이스에서 간선을 가져오는 방법이 달라진다.

대안 1

노드 레포지토리에 이미 정의된 get_all_nodes() 메서드를 유스케이스에서 사용한다.

  • aggregate에 대한 규칙을 완벽히 준수하게 된다.
  • 영속성 쿼리 성능 저하 가능성이 존재한다.

대안 2

노드 레포지토리에 메서드 get_all_edges() 메서드를 정의하고, 유스케이스에서 사용한다.

  • 단순 조회 기능이므로 aggregate 설정 목적인 엔티티 데이터 정합성 문제로부터 자유롭다.
  • 깔끔한 쿼리를 작성할 수 있다.

[질문] 지도관리자 간선 생성, 수정, 삭제 API에서 에러 응답 상태 코드

입력: 노드 ID 두 개

생성

  • 두 노드 ID에 노드가 존재하지 않는 게 하나 이상 존재: 400
  • 두 노드 ID가 같음: 400
  • 두 노드 ID에 해당하는 노드 간에 이미 간선이 존재: 400

수정, 삭제

  • 두 노드 ID에 노드가 존재하지 않는 게 하나 이상 존재: 400 vs. 404
  • 두 노드 ID가 같음: 400 vs. 404(아래 경우에 포함시켜서 해석)
  • 두 노드 ID에 해당하는 노드 사이에 간선이 존재하지 않음: 400 vs. 404

[정리] Use Case, Presenter, API 간 의존성 구현

정책

  • use case에서 presenter의 구현과 view model의 존재에 대해 몰라야 한다. (계층화 원칙)
  • api는 output data에 대해 모르면 좋다. (클린 아키텍처 도식에 의거)
  • use case와 presenter가 서로 아예 모르는 것보다 use case에서 presenter의 인터페이스를 사용하고, 구현체를 주입 받는 형태가 좋다. (클린 아키텍처 도식에 의거)
  • api에서 주입 받는 presenter와 use case의 관계를 알고 있으면 좋다. (코드 가독성)

구현 옵션

use case가 반환하는 값

  • use case가 output data를 반환하기
  • use case가 view model을 반환하기
  • use case가 주입 받은 presenter를 호출하고 None을 반환하기 ✅

use case가 presenter을 주입받는 방식

  • use case가 __init__()에서 presenter 입력 받기
  • use case가 execute()에서 presenter 입력 받기 ✅

presenter가 생성한 view model에 접근하는 방법

  • presenter.present()가 view model을 반환하기
  • presenter.present()에서 인스턴스 변수에 view model 할당하고 접근 메서드 따로 정의하기
    • presenter의 view model 인스턴스에 접근하는 메서드를 인터페이스에 정의하기
    • presenter의 view model 인스턴스에 접근하는 메서드를 구현 클래스에 정의하기 ✅

use case에 presenter를 주입하는 위치

  • main에서 use case에 presenter 주입하기
  • api에서 use case에 presenter 주입하기 ✅

api에서 presenter를 의존하는 방식

  • api에서 presenter 구현체를 주입 받기
  • api에서 presenter 구현체를 생성하기 ✅

[테스트] 모킹과 타입 힌트

@pytest.fixture()
def mock_node_repo() -> mock.Mock:
    mock_node_repo: mock.Mock = mock.Mock(spec_set=NodeRepository)
    mock_node_repo.get_all_nodes.return_value = [
        Node(name='A', point=Point(longitude=Decimal('1.0'), latitude=Decimal('2.0'))),
        Node(name='B', point=Point(longitude=Decimal('3.0'), latitude=Decimal('4.0'))),
    ]
    return mock_node_repo


@pytest.fixture()
def mock_list_nodes_presenter() -> mock.Mock:
    return mock.Mock(spec_set=ListNodesOutputBoundary)


def test_list_nodes(
    mock_node_repo: mock.Mock,
    mock_list_nodes_presenter: mock.Mock,
) -> None:
    ListNodesUseCase(
        node_repo=mock_node_repo,
        output_boundary=mock_list_nodes_presenter,
    ).execute()
    
    assert mock_node_repo.get_all_nodes.called
    assert mock_list_nodes_presenter.present.call_args_list == [
        mock.call(
            output_data_list=[
                ListNodesOutputData(name='A', longitude=Decimal('1.0'), latitude=Decimal('2.0')),
                ListNodesOutputData(name='B', longitude=Decimal('3.0'), latitude=Decimal('4.0')),
            ],
        ),
    ]

[질문] Presenter 역할

API에서 응답 명세의 표현?
응답에 필요한 데이터의 묶음?
Django나 FastAPI 같은 웹 프레임워크에 종속적인가?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.