GithubHelp home page GithubHelp logo

doodle-doodle's Introduction

Ahn Siwon @ssibongee

Hits Wiki LinkedIn Gmail

Hello, I am a server developer who enjoys tackling complex business problems through code.

Currently, I am working at Toss Place, a company at the forefront of innovating offline payments in Korea.

At Toss Place, we are seeking colleagues to join us in revolutionizing the offline payments industry. We would be delighted if you could join us.

Experience

Server Developer at Toss Place  (Full-time, Jan 2023 ~)

Server Developer at Channel Corp  (Full-time, Aug 2021 ~ Dec 2022)

doodle-doodle's People

Watchers

 avatar

doodle-doodle's Issues

도메인 모델과 엔티티를 분리해야할까

  • 페이먼츠의 김재민님 처럼 도메인 요구사항으로부터 개념 모델을 잘 도출하는 경우는 의미있다고 봄
  • 그와 별개로 대부분의 경우에는 도메인 모델과 엔티티를 분리하는 것에 대해 큰 이점을 가져가지 못함 거의 대부분 1:1 대응
  • 특히나 JPA를 사용하게되면 도메인 모델과 엔티티 모델을 분리함으로써 여러가지 기술적인 불편함이 존재함, 엔티티를 로드할 때 연관관계에 있는 엔티티를 즉시 로딩 해야한다거나 등
  • 물론 DDD 관점에서 애그리거트 단위로 잘 설계된 도메인 엔티티의 경우는 즉시 로딩하는게 당연한거고, 이러한 관점에서 잘 설계한다면 굳이 지연 로딩 할 필요있는지라는 이야기를 할 수 있지만 실제 필드에서 문제를 해결하다보면 현실적 제약사항이 존재함
  • 그와 별개로 모듈들이 잘 나뉘어져 있는 경우 모듈 간의 경계 및 의존성 방향을 위해서 도메인 모델과 엔티티를 분리할 수 있을 것 같음
  • 그럼에도 불구하고 최근에 든 생각은 도메인에 대한 이해도가 조금 더 높아지면 높아질 수록 좀 더 코어한 개념 모델을 추출하기 쉬워져서 오히려 도메인 모델과 엔티티가 1:1 대응하지 않는 경우가 빈번히 생긴다는 것, 그리고 애플리케이션 로직에서 엔티티를 넘기는 상황에서는 트랜잭션 범위 등 고려해야할 부분이 많은데 도메인 모델로써 돌아다니는 것은 그에 대한 고민이 상대적으로 적다는 것
  • 가장 최근에 작업하고있는 코드에서 이벤트의 프로퍼티로 도메인 모델을 넘기고 있었는데 이 때 사용한 도메인 모델은 엔티티와 별도로 분리되지 않은 도메인 모델, 이벤트에 실어져서 도메인 모델이 이리 저리 옮겨 다니면서 트랜잭션 범위나 JPA의 영속성 컨텍스트를 고려해야하는 것이 생각보다 불편했음, 도메인 모델이 엔티티와 분리되었다면 좀 더 이러한 고민으로부터 자유롭지 않았을까

패스워드 인코딩 알고리즘

Bcrypt

  • 블로우 피쉬 암호화 알고리즘을 기반으로한다.
  • 레인보우 테이블 공격과 같은 사전에 계산된 해시 공격을 방지하기 위해 각 패스워드 해시에 대해 자동으로 솔트를 생성하고 관리한다.
  • 비용 요소(또는 작업요소)를 설정할 수 있으며, 이는 해시를 계산하는데 필요한 시간과 자원의 양을 결정한다. (비용 요소가 높을수록 해시를 생성하고 검증하는데 많은 시간과 CPU 자원이 필요하다, BF 공격에 대한 저항력을 증가시킴)
  • 하드웨어 성능이 향상됨에 따라 작업 요소를 증가시켜 해시 함수의 난이도를 조절할 수 있으며 이를 통해 미래 기술 발전에 대비한 보안성 유지가 가능하다.

PBKDF2(Password-Based Key Derivation Function 2)

  • 키 스트레칭 기술을 사용하는 해시 알고리즘이다. (패스워드를 키로 변환하는데 사용되는 알고리즘)
  • 솔트는 별도로 생성되어야하며 해시와 함께 저장되어야한다.
  • PBKDF2는 반복 횟수를 설정하여 해시 계산에 필요한 시간과 자원의 양을 결정한다. 높은 반복횟수는 해시를 생성하는데 더 많은 시간이 걸리게하여 BF 공격을 어렵게함.
  • 반복 횟수를 높게 설정하면 PBKDF2도 매우 안전해지지만 GPU를 이용한 공격에 더 취약할 수 있음

선택 기준

  • Bcrypt는 일반적으로 웹 애플리케이션에서 많이 사용하며, 기본적인 보안 요구사항에 적합하다. PBKDF2는 키 생성이 중요한 시나리오에서 더 유리할 수 있으며 더 많은 설정 옵션을 제공한다.
  • Bcrypt는 레인보우 테이블 공격에 강하며, PBKDF2는 높은 반복 횟수 설정시 브루트 포스 공격에 강하다.
  • PBKDF2는 주어진 패스워드에 대해서 ex. SHA-256 등의 해시 함수를 반복적으로 적용한다.(반복 횟수는 해시 계산에 필요한 시간과 자원을 증가시킴), PBKDF2는 기본적으로 반복 결과를 저장하기 위한 메모리 공간이 필요하며 횟수가 증가할수록 메모리 사용량이 늘어날 수 있다.
  • Bcrypt는 블로우 피쉬 알고리즘을 기반으로 설계되어 고정된 양의 메모리를 사용한다. (각각의 해시 함수 결과 저장을 위한 메모리는 PBKDF2가 적음)
  • PBKDF2는 CPU 성능에 의존하는 알고리즘이고, 반복적으로 해시 함수를 사용하는 동작 방식은 GPU에서 매우 효율적으로 계산될 수 있다. 특히 PBKDF2 연산은 GPU 상에서 병렬로 수행될 수 있어 BF 공격 속도가 크게 증가할 수 있다.
  • 반면 Bcrypt는 메모리 접근 패턴과 관련하여 상대적으로 복잡한 알고리즘이기 때문에 GPU에서 효율적으로 수행되기 어렵다.

계층형 테이블 구조

SQL 안티 패턴 3장 Naive Tree

문제 정의

  • 위와 같은 계층형 테이블 구조의 문제는 단순하지만 하나의 SQL 쿼리를 가지고 트리 전체의 데이터를 불러오기 어렵다는 점, 단지 고정된 깊이까지(자식 또는 손자 정도되는 데이터까지), 데이터의 깊이가 무제한인 경우에 모든 데이터를 얻기 위해서는 많은 SQL을 실행해야함.
  • 다른 방법으로는 전체 데이터를 불러온 다음 애플리케이션 메모리 안에서 트리 구조를 구성하는 것, 하지만 데이터가 많고 읽기 트래픽이 많은 서비스에서는 매번 데이터를 조회할 때마다 수십 수백만개의 데이터를 정렬하는 것이 비현실적이다.
  • 다음과 같은 이야기를 한다면 Naive Tree 안티 패턴이 사용되고 있음을 알 수 있다.
    • "트리에서 얼마나 깊은 단계를 지원해야하는가", 재귀적 쿼리를 사용하지 않고 어떤 노드의 모든 자손 또는 조상을 얻기 위해 노력하고 있음, 제한된 깊이까지만 지원하는 것으로 타협할 수 있지만 이것은 어느정도 깊이까지 지원해야하는지에 대한 문제를 낳음.
    • "트리 데이터 구조를 관리하는 코드를 수정하는 것이 두려움", 계층 구조를 관리하는 좀 더 정교한 해법 중 하나를 사용했지만 잘못된 것을 사용하고 있음. 각 기법은 트레이드 오프를 가지고 있기 때문에 상황에 맞는 최선이 아닌 다른 방법을 선택한 것일 수 있음.
    • "트리에서 고아 노드를 정리하기 위해 주기적으로 스크립트를 실행해야함", 애플리케이션에서 트리에서 자식이 있는 노드를 삭제하면서 연결이 끊긴 노드가 생김, 복잡한 데이터 구조를 데이터베이스에 저장할 때, 변경 이후에도 일관성 있는 상태로 데이터 구조를 유지해야함.
  • 그럼에도 불구하고 주어진 노드의 자식 또는 부모 노드 탐색 정도로만 제한되고, 데이터가 그렇게 많지 않다면 위와 같은 인접 리스트 방식이 합당할 수 있음.
  • 인접 리스트 형식으로 저장된 계층 구조를 지원하기 위한 SQL 확장 기능을 사용할 수 있지만(WITH 쿼리에 CTE를 사용) MySQL 등의 일부 DBMS에서는 이와 같은 문법을 제공하지 않음.

해결 방법 1. Path Enumeration

  • 인접 리스트의 문제 중 하나는 트리에서 주어진 노드의 조상들을 얻는데 비용이 많이 든다는 것이다.
  • Path Enumeration 방식은 일련의 조상을 각 노드의 속성으로 저장해서 이를 해결한다. ex. 디렉터리 구조에서 경로를 /usr/local/lib 등과 같이 표시하는 것
  • ex. 경로 구분자로 /를 사용했을 때 각각의 모든 조상과 자손을 구하는 쿼리 예시
SELECT *
FROM comments AS c
WHERE '1/4/6/7/' LIKE c.path || '%';
  • 위와 같은 쿼리로 조상의 경로로 만든 패턴 1/4/6/%, 1/4/%, 1/%이 매치된다.
  • 인수를 반대로하면 모든 후손 정보를 조회할 수 있다.
SELECT *
FROM comments AS c
WHERE c.path LIKE '1/4/' || '%';
  • 서브 트리에서 노드의 비용 또는 수를 세는 것과 같은 쿼리도 손쉽게 해결할 수 있다.
  • ex. 댓글 4부터 시작하는 서브트리에서 작성자 마다 답글 수를 카운트하는 쿼리
SELECT COUNT(*)
FROM comments AS c
WHERE c.path LIKE '1/4/' || '%'
GROUP BY c.author;
  • 새로운 노드를 삽입하는 방법은 인접 리스트를 사용할 때와 비슷하지만, 데이터베이스에서는 경로가 올바르게 형성되도록 하거나 경로 값이 실제 노드에 대응되도록 강제할 수 없다.
  • 경로를 유지하는 것은 애플리케이션 코드에 종속되며 이를 검증하는데 비용이 많이 들게된다. (VARCHAR 컬럼 길이를 아무리 길게 설정하더라도 결국 제한이 있기 때문에 엄격하게 말하자면 지원할 수 있는 트리의 높이도 제한됨)

해결 방법 2. Nested Sets

  • 각 노드가 자신의 부모를 저장하는 대신 자기 자손의 집합에 대한 정보를 저장함.(이 정보는 트리의 각 노드를 두 개의 수로 인코딩해서 나타낼 수 있음, nsleft, nsright로 표현)
  • 각 노드의 nsleft 수는 모든 자식 노드의 nsleft 수보다 작아야한다. nsright는 모든 자식의 nsright 수보다 커야한다.
  • 이 값을 할당하는 가장 쉬운 방법 중 하나는 트리를 깊이 우선 탐색하면서 값을 하나씩 증가시켜가면서 할당하는 것이며, 자손으로 한 단계씩 내려갈 때는 nsleft 값을 할당하고 가지를 한 단계씩 올라갈 때는 nsright에 값을 할당한다.
image - ex. 댓글 6과 그 조상은 `nsright` 값이 현재 노드의 숫자 사지에 있는 노드를 검색해서 얻을 수 있다. ```sql SELECT c2.* FROM comments AS c1 JOIN comments AS c2 ON c1.nsleft BETWEEN c2.nsleft AND c2.nsright WHERE c1.comment_id = 6 ``` - 중첩 집합 모델의 장점 중 하나는 자식을 가진 노드를 삭제했을 때 그 자손이 자동으로 삭제된 노드 부모의 자손이 된다는 것이다.(노드를 삭제해 값들 사이에 간격이 생기더라도 트리 구조에는 아무런 문제가 없음) - 그러나 자식이나 부모를 조회하는 것과 같이 인접 목록 모델에서 간단했던 일부 쿼리가 중첩 집합 모델에서는 더욱 복잡해짐. - 중첩 집합 모델에서는 노드를 추가 또는 이동하는 것과 같은 트리 조작도 다른 모델을 사용할 때보다 복잡하며, 새로운 노드를 추가한 경우 새 노드의 왼쪽 값보다 큰 모든 노드의 왼쪽, 오른쪽 값을 다시 계산해야한다. - 중첩 집합 모델은 각 노드에 대해 조작하는 것보다는 서브트리를 쉽고 빠르게 조회하는 것이 중요할 때 가장 잘 맞는다.

해결 방법 3. Closure Table

  • 계층 구조를 저장하는 단순한 방법으로 부모 자식 관계에 대한 경로만을 저장하는 것이 아니라 트리의 모든 경로를 저장한다.
  • 트리 구조에 대한 정보를 별도의 테이블에 저장하고, 이 테이블에는 트리에서 조상 자손 관계를 가진 모든 노드의 쌍을 하나의 행으로 저장한다.(또한 각 노드에 대해 자기 자신을 참조하는 행도 추가함)
  • 위의 테이블에서 조상이나 자손을 가져오는 쿼리는 중첩 집합에서보다 훨씬 더 직관적이다.
  • ex. 댓글 4의 자손을 얻으려면 테이블에서 조상이 4인 행을 가져오면된다.
SELECT c.*
FROM comments AS c
JOIN paths AS t ON c.comment_id = t.descendant
WHERE t.ancestor = 4;
  • 터미널 노드에 새로운 자식을 추가하기 위해서는 먼저 자기 자신을 참조하는 행을 추가한 다음 경로 테이블에 터미널 노드를 자손으로 참조하는 모든 행을 복사해 자손을 새로운 노드 아이디로 바꿔놓는다.
  • 터미널 노드를 삭제할 떄는 경로 테이블에서 터미널 노드를 자손으로 참조하는 모든 행을 삭제한다.(경로 테이블에서 행을 삭제한다고해서 댓글 자체를 삭제하지는 않음)
  • 서브 트리를 트리 내의 다른 위치로 이동하고자 할 때는 먼저 서브 트리의 최상위 노드와 그 노드의 자손들을 참조하는 행을 삭제해 서브 트리와 그 조상의 연결을 끊은 다음, 새로운 위치의 조상들과 서브트리의 자손들에 대응하는 행을 추가해서 고아가된 서브트리를 붙인다.
  • 클로저 테이블 모델은 중첩 집합 모델보다 직관적이다. 조상과 자손을 조회하는 것은 두 방법 모두 쉽고 빠르지만, 클로저 테이블이 계층구조 정보를 유지하기 쉽다.
  • 부모나 자식을 좀 더 쉽게 조회할 수 있도록 경로 테이블에 길이 속성을 추가해 클로저 테이블을 개선할 수 있는데, 자기 자신에 대한 길이는 0, 자신에 대한 길이는 1, 손자에 대한 길이는 N과 같은 식이다.

언제 어떠한 모델을 사용해야하는가

  • 인접 리스트는 가장 흔히 사용되며 WITH 또는 CONNECT BY PRIOR을 이용한 재귀적 쿼리는 인접 모델을 좀 더 효율적으로 사용할 수 있지만 이 문법을 지원하는 DBMS가 한정적이다.
  • 경로 열거 모델은 경로를 사용자에게 보여줄 때 좋지만 참조 정합성을 강제하지 못하고 정보를 중복 저장하기 때문에 깨지기 쉽다.
  • 중첩 집합은 영리하지만 역시 참조 정합성을 지원하지는 못하며, 트리를 수정하는 일이 거의 없고 조회를 많이 하는 경우에 적합하다.
  • 클로저 테이블은 가장 융통성 있는 모델이고 한 노드가 여러 트리에 속하는 것을 허용하는 유일한 모델이다. 관계를 저장하기 위한 별도의 테이블이 필요하다. (깊은 계층 구조를 인코딩하는데 많은 행이 필요하고, 계산을 줄이는대신 저장 공간을 많이 사용하는 트레이드 오프가 존재함)

모듈과 레이어 어떻게 나눌 것인가

  • 최근 회사의 다른 신규 프로젝트들처럼 하나의 도메인 서버를 멀티 모듈로 구성하고 있음.
    • api, application, domain, infrastructure, sdk 같은 형태
  • 프로젝트를 여러 모듈로 구성하다보니까 좀 더 모듈과 계층 간의 의존성 방향과 응집도에 초점을 맞춰서 개발하게 됨.
  • 이렇게 모듈을 여러개로 나누었을 때 극명한 단점은 중복 코드와 매핑 함수가 너무나도 많이 생긴다는 것, 각각의 모듈 마다 별도의 모델을 정의해두고 매핑하는 과정 때문에, 적당한 타협이 필요함
  • 각 모듈 마다 모델을 분리해서 사용하는 것이 의미있는가라고 했을 때 각 모듈간의 높은 응집도 유지와 모듈로 강제되는 의존성 관리를 위해서 정도, 특히 outside-in의 경우 외부 세계의 데이터를 도메인 내의 특정 모델로 매핑해서 쓰는 경우는 유의미하다고 생각함.
  • 회사의 다른 몇분들에게 어떻게 이러한 것들을 사용하고 계신지 물어봤는데 세가지 답변을 얻을 수 있었고 이것들을 정리해보면 다음과 같음.
  • A
    • 도메인 모듈에 큰 영향이 있지 않다면 데이터 모델을 공유하나 모듈마다 따로 만들어서 사용하는지 큰 차이없다고 생각함. (단, 전제는 모듈 간의 의존성 방향이 사이클을 이루지 않고 일관되게 유지한다는 전제, 이렇게되면 내부에 의존하고 있는 쪽의 데이터 모델을 공유해서 사용하는 형태)
    • 데이터 모델을 잔뜩 만들어두면 귀찮기도하고 한번 수정하면 연쇄 수정이 일어나는 경우가 많은 것 같아서 계층별로 나누는게 불필요함.
    • 이러한 데이터 모델이 애플리케이션 서비스의 요청과 응답 모델이라면 application 모듈에 위치할 것이고 외부 API 호출 용도라면 infrastructure에 위치할 것 같음.
  • B
    • 데이터 모델이 계층 또는 모듈 간의 경계를 넘나들기 위한 용도라면 shared 보다 적절한 자리를 찾을 수 있을 것 같음.
    • 그게 아니라 어떠한 값 타입이라면 shared에 존재하는게 맞을 것 같음.
    • "만들면서 배우는 클린 아키텍처"에서 이야기한 것처럼 "최대한 맵핑하지 않기 전략"을 사용하다가 정말 필요해지면 계층별로 나누는 방식을 사용하긴함.
    • 이렇게 나누고나면 A가 이야기한것처럼 연쇄 수정이 일어나는 경우가 있긴한데 자주 발생한다거나 엄청 짜친다거나하진 않아서 괜찮다고 생각함.
    • 개인적으로도 ~Dto와 같은 네이밍을 좋아하지 않는데 최대한 적절한 이름을 찾고자 노력함 ~Request/Response, ~Command, ~Query, ~Result와 같은 이름을 사용하면 적어도 좀 더 의미있는 것 같음.
  • C
    • 보통 계층을 나누면 대체로 아래와 같이 나누고 있음.
      • domain < application < presentation
      • domain < infra, application < infra, presentation < infra
    • 결국 infrastructure는 모든 모듈을 알고 접근할 수 있도록 만들어주긴함. (기술적인 구현 상세를 다루는 영역이라서)
    • "여러 계층에 공유하는 데이터 모델은 어느 모듈에 위치해야하는가"라는 물음에 위처럼 의존성을 나누면 항상 바깥 쪽에서 안쪽을 볼 수 있는 형태가 되는데 이렇게되면 shared 같은 모듈이 사실 별로 의미없음. (domain에 넣으면 모든 모듈에서 참조가능하기 때문에)
    • shared가 유의미할 때는 이런 계층이 피처 단위로 나뉘어져 있을 때 공유하는 경우에만 의미가 있음.
    • 계층을 넘어가는 모델이 필요하다면 기본적으로 그 모델을 사용하는 책임과 가장 가까운 친구 옆에 우선 붙여주고 바깥 모듈에서 안쪽 모듈의 모델은 자유롭게 사용하도록 시작함.
    • 그리고 이 둘이 달라져야하는 경우가 생긴다면 그 때 바깥 모듈에서의 별도의 모델을 만들어서 매핑해서 사용하는게 좀 더 효율적으로 계층을 잘 나누어 사용하는 방법 같음.

JPA와 연관관계

  • https://www.youtube.com/watch?v=vgNHW_nb2mg&ab_channel=%EC%A0%9C%EB%AF%B8%EB%8B%88%EC%9D%98%EA%B0%9C%EB%B0%9C%EC%8B%A4%EB%AC%B4
  • 연관관계를 거의 맺지 않음, ID 매핑을 사용하고 FK를 사용하지는 않음.
  • 만약 연관관계를 사용한다면 다음의 지침을 적용한다.
    • 양방향 매핑을 사용하지 않는다, 단방향 매핑을 사용한다.
    • 단방향도 OneToOne, OneToMany는 잘 사용하지 않는다.
    • 라이프사이클 주기가 완전히 같다면 고민한다.
    • 충분한 고민 이후 연관관계 매핑을 사용한다면 단방향 ManyToOne을 보통 사용한다.
  • ID로만 걸어놓으면 그 다음에 전략을 선택하는 것은 쉽지만, 연관관계 매핑을 떡칠해놓으면 답이 없음.
  • 특히 양방향 매핑 + 엔티티를 도메인 모델로 사용하는 최악의 경우에는 코드 수정하기가 매우 고통스럽다.
  • 리뷰가 있고 리뷰 이미지가 있다고 할 때, 리뷰가 있다고해서 이미지가 항상 있는가? 사실 0..N이면 연관관계가 없다고 볼 수 있음.

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.