Skip to main content

· 2 min read
  1. Mapper란?
  2. (싱글톤 객체 + 의존성 주입)으로 객체를 의존하는 것과 static 메소드로 선언하여 의존, 사용하는 것의 비교
    • static 메소드를 사용하면 프레임워크에서 지원하는 의존성 주입 기능을 사용하지 못한다.
    • static 메소드는 보통 유틸성 객체에 유용하게 사용된다.
    • 동일한 클래스에 대해서 동일한 상태값을 보장하기위한 목적으로 static을 사용할 수 있다.
    • static 메소드는 객체지향적인 기능은 아니라고 볼 수 있을 것 같다.
  3. 클래스와 인스턴스의 생성 위치와 주기. static 변수와 메소드와 인스턴스 변수, 메소드의 생성 위치와 주기 비교.
  4. 알림 내용 템플릿 데이터를 enum에 저장 or db에 저장.
    1. https://jojoldu.tistory.com/137

· One min read
  • 단순 조회기능을 한다면 표현영역에 로직 구현
  • 권한 검사는 요구사항에 따라 표현영역, 응용서비스, 도메인 수준에서 이뤄질 수 있다.
  • 값 밸리데이션에 대해서 표현영역에서는 값의 형식에대해 검사, 응용서비스에서는 값의 유무등 논리적인 검사.
  • 응용서비스에 검사하는 값이 여러개일때 해당 값을에 대한 예외처리를 한번에 내려주기위해 exception에 인자를 리스트로 담아 내려주는 방식도 있다.
  • domain service와 repository가 특정 기능에 묶인다면, interface를 두고, infrastructure ?영역에 구현체를 두도록해서 결합도를 낮춰라.

· 2 min read
  1. 응용 서비스의 클래스 는 최대 2-3개의 기능이 들어있는 정도로, 중복 코드가 생긴다면, 해당 로직도 별도 클래스로 구현. 책에서 예시로는 응용서비스이름 +Helper라는 이름의 클래스로 정적 메소드로 공통 로직을 처리. 사용하는 응용서비스에서는 의존성 주입을 받는 것이 아니라 정적 메소드 호출해서 사용.
  2. 응용 서비스의 인터페이스를 만드는 것에 대해서 구현 클래스가 여러개라면 인터페이스를 만드는 것이 좋겠지만 경험적으로 응용 서비스가 구현 클래스가 여러개이거나 런타임 때 교체할 일이 거의 없다고 한다. 테스트 코드를 작성할 때에도 mockito와 같은 라이브러리를 사용하면 구체 클래스에 대해서도 목개게를 만들어주기 떄문에 응용 서비스에서 인터페이스 사용은 거의 안하는 것을 제안함. - 도메인 주도 개발 시작하기 / 최범균

· One min read
  1. Specification interface in DDD and JPA
  2. JPA 동적 인스턴스 생성.

  1. fcm에서 전송하면 해당 디바이스에서 어떻게 표시되는 것이지 ? 가령 위퍼블릭 앱이 켜져있으면 앱에서 보여준다 ?

  1. lock
    • shared lock (for share)
    • exclusive lock (for update)
    • 비관적 락, 낙관적 락.
    • 락의 범위, 락 해제 시점.
  2. msa
    • 분산 트랜잭션

· 2 min read
  1. 도메인에 대한 로직을 응용 서비스에서 분리하라. 응집도가 떨어지고 중복 코드가 많아질 수 있으며 그러면 변경이 용이하지 못하게 되는 등 소프트웨어의 가치가 떨어지기 때문이다. In DDD.
    • 책에서는 도메인에 대한 로직은 도메인 클래스(entity)에 구현함.
    • 도메인
  2. 도메인 서비스
    1. 여러 애그리거트가 필요한 도메인 로직의 경우 특정 도메인에 우겨넣는 것 보다는 차라리 서비스를 분리하기.
    2. 서비스의 이름은 도메인의 의미가 드러나는 용어를 타입과 메서드로.
  3. 응용 서비스에서 로직은 레포지토리, 도메인의 메소드, 도메인 서비스의 조합으로 이루어질 것이다.

· One min read
  • callback, Proimse object, async/await
  • then(), catch()
  • then(), catch()는 Promise객체의 메소드로 이해하고 있는데, contractSevice 객체에서 .catch()가 안먹혔었다. 이유는 ? then이 없어서 ?
  • 콜백함수는 리턴이 필요없다 ?

  • JPA에서는 jpa transaction 안에서 도메인이 변경되면 자동으로 업데이트 해준다 ?

· One min read
  1. "우세종이라고 해서 우리가 그 행성의 주인은 아니에요." - 닭강정
  2. 힘이 세다고 모든 사람이 그 힘을 쓰지는 않는다. - 닭강정
  3. 닭강정에서 인류보다 발달한 외계인은 배려와 친절을 배푸는 방향으로 진화했다!

· 2 min read
  1. value-object pattern
    1. feature. make and return new object when member value is changed.
  2. 백엔드 언어와 프레임워크 사용 추세.
    • 한국에서는 go, kotlin(spring)으로 신규프로젝트 진행을 많이 하는 추세.
    • kotlin은 과거부터 많이 사용된 java 기반의 spring과 완벽히 호환되기 때문에 전환이 비교적 쉬우나, JVM 기반으로 동작하기 때문에 싫어하는 사람도 많은 것 같다.
      • kotlin은 멀티플랫폼언어이기 때문에 JVM을 사용하지 않고 NATIVE로 바로 컴파일되는 것의 성능이 좋아지면 또 분위기가 바뀔지도 ?
      • kotlin이 어떤 원리로 여러 것들로 컴파일될 수 있는지이해해보자, 그리고 이런 독보적인 특징으로 발전 가능성이 어느정도까지 일지 판단해보자.
      • kotlin을 사용하면 syntax sugar? 언어단에서 로직을 단순화 하고 아기자기하게 다룰 수 있다. (go는 그런 맛이 없다고 한다.)

· One min read
  • nest의 객체 등록, 의존성 주입을 통한 객체 사용은 메모리에 부하를 줄 수도 있을 것이다.
    • 불필요하게 남용 X
    • 메소드가 return되어도 객체가 제거되지 않을 것이기 때문.
  • 다른 하드웨어로의 IO 통신은 시간이 어느정도 줄이는데 한계가 있다. IO를 줄이는 것이 성능 개선에 중요.
    • ex 백엔드 서버와 DB 서버간의 소통. 백엔드 서버와 블록체인 간의 소통.

· One min read
  • trpc, grpc
  • promise.all
  • await, async
    • async await을 남용하지말자. 비동기로 동작해도 되는 로직은 굳이 붙일 필요없다.

· One min read
  1. declaring type of typescript
    • 타입스크립트에서 함수도 타입으로 지정이 가능하다. 다른 언어도 가능한가 ? 어떤 원리인가 ?

· One min read
  1. 서비스에 알림기능이 도입된다. 알림 종류는 sms 푸쉬 알림만 있다. 알림 케이스는 20가지 정도, 알림 서버 별도로 필요할까 ?

· One min read

frontend도 많이 개발해보자.

- 개인적으로 하고 싶은 사이드 프로젝트를 하는데 도움이 될 것이다.
- 혼자서 아이디어를 구현할 수 있는 능력이 생기게 될 것이고, 나는 나의 아이디어를 빠르게 구현하는 것을 원한다.

· One min read
  1. 모듈을 global로 import받거나, 사용하지 않는 객체도 제공하는 module을 import 받았을 때 성능상 이슈는 없나 ?
    • 사용하지 않아서 인스턴스 생성이 안되니깐 ?
  2. nestjs에서 module을 효율적으로 관리할 수 있게 설계하는 방법.
    • 중복해서 import하지 않도록.
    • 사용하지 않는 객체를 import 하지 않도록.
  3. TypeError: Class extends value undefined is not a function or null
  4. repository와 service 사이에 data service 객체를 활용하는 것에 대한 best practice 혹은 관련 아키텍처가 궁금하다.

· 5 min read
  • 모듈 구조를 세분화하는 것에 있어서 정해진 규칙은 없다. 최범균님 개인적으로는 한 패키지에 가능하면 10 ~ 25개의 타입 개수를 유지하도록 하려고한다. 이를 넘어서면 패키지를 분리한다. (도메인주도설계시작하기 - 최범균)
  • 애그리거트
    • 도메인들을 상위 수준에서 조망할 수 있게 하는 개념.
    • 도메인 규칙에 따라 함께 생성되는 구성요소는 하나의 애그리거트에 묶일 가능성이 크다.
    • A가 B를 갖는다고 하면 A와 B 구성요소가 같은 애그리거트에 속한다고 생각하기 쉽지만, 각각의 변경이 서로에 영향을 주지 않는다면 같은 애그리거트에 속한다고 보기 어렵다.
    • 최범균님의 경험 상 애그리거트가 한 개의 엔티티 객체만 갖는 경우가 많으며 두개 이상의 엔티티로 구성되는 경우는 드물었다고 한다.
    • 애그리거트 루트는 애그리거트에 속한 도메인들의 일관성을 유지시켜주는 대표 도메인이다.
      • 애그리거트 외부에서 애그리거트에 속한 객체를 직접 변경하면 안되며 루트 도메인을 거쳐 변경해야한다.
      • 애그리거트 루트를 통해서만 도메인 로직을 구현하게 만드려면
        • set메소드 공개범위로 x
        • 밸류 타입은 불변으로 를 습관적으로 적용해야한다.
    • DB 트랜잭션 범위는 작으면 작을 수록 성능상의 이점을 가져간다. 따라서 하나의 트랜잭션에서 하나의 애그리거트만 수정하도록 하는 것이 좋다. (성능 상의 이점을 가져가는 이유는 한번에 처리하는 테이블 수가 줄어듦으로서 잠금하는 대상이 줄어들고 동시에 처리할 수 있는 트랜잭션이 많아진다는 의미기이 때문.)
    • 레포지토리도 하나의 애그리거트당 하나만 만든다! (order와 orderLine 은 db에 테이블은 다로 있지만 레포지토리 객체는 하나만 만든다.)
    • ID를 이용한 애그리거트 참조
      • 객체간에 db 테이블간의 연관관계를 가진 것처럼 ORM을 사용할 경우 onetomany등의 어노테이션으로 객체간에 참조를 가능하도록 할 수 있지만, 이는 자칫하면 다른 애그리거트와의 결합도를 높일 수 있게된다. 이외에더 몇가지 문제가 더 있을 수 있으며 이를 완화하기위해 ID로 참조하도록 한다. 쉽게 코드로 생각하면 객체를 멤버변수로 지정하고 oneTomany등의 어노테이션을 사용하는 것이 아닌 참조하려는 객체의 id값을 멤버변수로 가진다. (다른 애그리거트 사이에는)
    • 객체 참조 방식을 사용하는 대신 ID 참조 방식을 사용하면 조회 시 성능이슈가 발생할 수 있는데, ID로 참조된 객체까지 조회하려면 N개의 도메인을 조회했을 때 ID로 매핑된 N 개의 객체도 한번 더 조회해야하는 n+1이슈가 발생한다. 이는 지연로딩에서도 발생하며, 객체 참조 방식(즉시로딩으로)을 사용하지 않고 해당 이슈를 해결하려면 조회 용 별도의 DAO를 생성해 사용하면 된다.

· One min read
  • DTO Naming Convention
    • request, response라는 이름과 dto를 함께 사용?
    • name + httpmethod + request,response + dto 라는 형식으로 적용해봄.
    • dto 이름이 길어지게 된다.

· One min read
  • 구현이 아닌 추상화에 의존해야하는 이유.
  • 전략 패턴 (Strategy Pattern)
  • 서브 클래스(sub class), 구성(composition)
  • 데코레이터 패턴이 객체에 추가 요소를 왜 동적으로 더하는 거지 ?
    • 인스턴스를 인자로 넣어서 데코레이팅 할 수 있으니깐 ? (인스턴스는 런타임 환경에서 생성됨.)

· 2 min read
  • 객체지향적으로 코드를 설계하는 것
    • 굉장히 많은 노력과 시간을 요구하는 일이다. 디자인 패턴은 단순히 상속, 캡슐화, 다형성, 추상화, 등의 기본적인 객체지향 개념을 적용하는 것에서 더 나아가 여러 상황들에 대해 객체지향적으로 디자인하는 방법을 이미 여러 고민 끝에 정리한 개념이다.
  • 옵저버 패턴
    • 여러 객체의 데이터를 일관성 있게 관리할 수 있도록도 할 수 있을 것이고 동시에 여러 객체의 행위를 통제할 때도 사용할 수 있을 것 같다.
    • subject 구현 클래스가 observer 구현 클래스를 관리.
    • 한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체에게 연락이 가고 자동으로 내용이 갱신되는 방식으로 일대다 의존성을 정의한다.
  • 데코레이터 패턴
    • 슈퍼 클래스와 데코레이터 클래스들이 공통된 형식의 추상 데코레이터 클래스를 상속 받고(1), 데코레이터들은 메인 클래스의 기능에 추가 기능을 데코레이팅(2) 하는 것이 특징적이다.
    • 데코레이터 패턴을 적용하면 잡다한 클래스가 많아진다는 단점 존재하지만 데코레이터 패턴을 활용해서 만든 API는 사용자가 원하는 행동을 추가로 쉽게 적용할 수 있다.

· One min read

Notification System

  • type = sms, email, ios push notification, aos push notification
  • Use APNS(Apple Push Notification Service) When noti to ios.
  • Use FCM(Firbase Cloud Messaging) when noti to aos.
  • In China, Use Jpush, PushY instead of FCM.

· 3 min read
  • 다오 가입 온체인 로직을 호출할 때, 트랜잭션 전송을 서버에서 하고, 이 때 상용되는 어드민 지갑은 기존에 하나였다. 트랜잭션 전송 주체가 되는 지갑이 하나이면 동시에 다오 가입 api를 호출 했을 때, nonce too low 이슈가 발생하게 된다. 이를 해결하기 위해 어드민 지갑을 5개로 운용하고, 해당 5개 어드민 지갑의 사용 여부 값을 메모리(레디스)의 set 데이터 스트럭쳐를 활용하여 로직을 구현하였다. 따라서 유저는 5개의 어드민 지갑 중 사용되지 않는 지갑을 메모리에서 찾아와 온체인로직을 호출하게되며, 유저가 호출하는 시점에 5개 어드민 지갑이 모두 사용되고 있으면 유저는 리트라이를 해야한다.
  • 이 때, 유저가 동시에 호출하게되면 결국 메모리에 어드민 지갑 사용여부를 조회하는 시점도 거의 동시에 이뤄지며 따라서 두 유저가 동일한 어드민 지갑을 사용하여 호출하게 되어 nonce too low이슈가 발생한다.
  • 우선은 메모리에서 사용되고 있지 않은 어드민 지갑들을 찾아서 그 중 랜덤하게 하나를 사용하도록 했지만, 결국 두 유저가 동시에 동일한 지갑으로 호출할 가능성은 존재한다.
  • 5개의 어드민 지갑을 적절히 분산해서 유저에게 할당을 어떻게 할 수 있을까?
    • 메모리에 lock ?

· One min read
  • 온체인로직 오프체인 로직 순서로 동작시킬 때, 온체인 성공, 오프체인 실패하는 케이스가 안생기게 하려면 어떻게해야할까?
    • getPast와 같은 안전장치 로직을 통해 보완할 수 있다. 다만 위퍼블릭에서는 이벤트에서 얻을 수 있는 값만으로 오프체인로직을 처리할 수 있는지 불확실하며, 불가능 하다면 메모리나 db에 오프체인 실패 시 처리를 위해 임시로 값을 저장해놓을 필요가 있다.

· One min read
  • 타입스크립트의 type 이란 ?
    • type alias로 새로운 타입을 지정할 수 있고 두개의 함수를 하나의 타입으로 지정하는 것과 같은 것이 가능하다.
    • 다른 언어에도 이런 것이 있나 ?
  • 동기와 비동기
    • 동기화된 함수들은 순서대로 처리, 비동기화된 함수들은 순차적으로 처리되지 않음.
  • 콜백 함수
  • CASCADE DELETE
    • FK에 해당 옵션을 설정하면 부모테이블의 데이터를 삭제했을 때, 자식 테이블도 함께 삭제한다.

· One min read

next.js에서 page router와 app router

nextjs에서 router는 기능의 중심이되는 개념이다. router의 종류에는 page router와 app router가 있으며 app router는 서버 중심의 라우팅, page router는 클라이언트 중심 라우팅이다. 구현 방법으로 page router는 pages 디렉토리에서 파일의 경로에 따라 자동으로 라우팅이되며 app router는 src/apps 디렉토리를 사용한다.

[공식문서] https://nextjs.org/docs/getting-started/installation [관련 블로그글] https://velog.io/@jjunyjjuny/nextjs-13.4.0%EB%B6%80%ED%84%B0-%EC%95%88%EC%A0%95%ED%99%94%EB%90%9C-App-Router.-Pages-Router%EC%99%80-%EB%B9%84%EA%B5%90

· One min read

진정한 자유란 무엇인가. "지유란 타인에게 미움을 받는 것" 누근가에게 미움을 받는 것. 그것은 내가 자유롭게 살고 있다는 증거이자 스스로의 방침에따라 살고 있다는 증표다. - [미움받을 용기 / p192]

타인에게 미움받고 싶지 않은 마음은 인간에게 극히 자유로운 욕망이라고 한다. 칸트는 이러한 욕망을 '경향성'이라고 지칭했다고 한다. 누군가가 나를 미워한다는 것을 알게되었을 때, 스트레스와 고민이 생긴다. 위의 글은 미움을 받는 상황에 대한 새로운 관점을 깨닫게 한다.

· One min read

어떤 테이블의 컬럼이 추가되어야하는 상황일 때, entity에만 컬럼을 추가하고 조회와 같은 로직을 수행하면 (db에는 컬럼 추가 x) 해당 컬럼이 없다는 에러를 낸다.

반대로 db에있는 컬럼이 매핑되는 엔티티에 없더라도 에러를 내지 않는다.

특정 컬럼이 추가되는 경우 반드시 DB에 먼저 추가한 후, 엔티티를 수정하여 반영하자. 반대로 특정 컬럼을 수정, 삭제하는 경우에는 엔티티에 먼저 작업을 하고 db에 해당된 컬럼을 수정, 삭제하자.

· 2 min read

오브젝트 책을 읽다가 객체지향설계는 책임을 중심으로 객체들을 분할할 수 있고, 상태(데이터)를 중심으로 객체들을 분할 할 수 있다고 한다. 그리고 변경을 이유로 책임을 중심으로 하는 것이 바람직하다고 말한다.

데이터 중심으로 객체들을 분할하고 영화 예매 시스템을 구현해봄으로써 그 이유를 파악하는데 데이터 중심설계는 캡슐화가 완전히 되지않고, 높은 결합도와 낮은 응집도를 가짐으로써 변경이 발생했을 때 여러군데에 수정이 필요하였다.

그런데 읽다보니 서버 프레임워크에서 정한 구조에 따라 entity를 생성하고 service 레이어에서 관련 도메인에 대한 로직을 구현하는 것이 전형적인 데이터 중심의 설계인 것이라고 생각되었다.

DDD를 사용하면 서버 프레임워크로 코드를 작성할 때도 객체지향적으로 짤 수 있는 것일까 ? 아니면 이러한 서버 프레임워크에서 짜놓은 틀은 어쩔 수 없는 것일까? 혹은 이러한 서버 프레임워크의 구조는 객체지향적 설계의 개념을 고려할만한 대상이 아닌 것일까 ?

· One min read
  • k8s의 network 구성에 대해 파악해보았다.
  • service로 클러스터 내부 네트워크 설정을 할 수 있다.
  • 우리팀 서비스는 ingress로 외부와 클러스터간의 네트워크 설정이 되어있다.

  • 이미 저장되어있는지 체크하고 저장되어있지 않으면 저장하는 로직에 대해 동시에 두개의 서버에서 로직이 수행되면 체크를 하더라도 중복 저장될 수 있다. 이것을 예방하기위해 존재하는지를 체크할 때 where문에 들어가는 컬럼을 unique index로 설정하면 된다고 한다. unique index가 무엇인지 이번주에 인덱스 부분 스터디 준비하면서 공부해보자.

· 3 min read

우리 팀은 NestJS로 서버개발을 하고 있는데, 초반에 nest를 학습하면서 Spring과 아주 비슷해서 어느 순간부터 nest 학습을 안하다가, 몰랐던 개념을 알게되어 기록한다.

이름하여 Pipe.

  • 파이프는 보통 2가지의 목적을 위해 사용된다.

    1. validation(유효성 검사) - 클라이언트가 전달한 데이터가 유효한지 검사 및 예외 처리.
    2. transformation(변환) - 클라이언트가 전달한 데이터를 원하는 타입으로 변환.
  • 해당 처리 시점은 라우터 핸들러로 전달되기 전.

  • class-validator와 class-transformer 라이브러리와 함께 사용하면 더욱 편리하게 유효성 검사와 변환이 가능합니다.

  • 프로젝트에서 사용되는 부분

2023-05-04-1.png

  1. @Body, @Query, @Param등의 어노테이션에 인자값으로 Pipe를 넣어주면 handler 단위로 파이프를 설정할 수 있습니다.

2023-05-04-2.png

  1. CreateDaoDto를 살펴보면 @IsString() 등의 class-validator 데코레이터로 파이프가 유효성 검사의 목적으로 사용되는 것을 확인할 수 있습니다.

2023-05-04-3.png

  1. 프로젝트 단위로 main.ts 파일을 보면 useGlobalPipes메소드로 특정 Pipe를 전역적으로 설정할 수도 있습니다. transfrom: true를 옵션으로하여 생성해주어야 class-transformer 라이브러리 적용이 가능합니다.

2023-05-04-4.png

2023-05-04-5.png

  1. 쿼리 파라미터의 타입으로 활용되는 DaoParams를 살펴보면 class-transformer의 @Type 데코레이터로 전달받은 값을 Number 타입으로 변환하여 변환의 목적으로 사용되는 것을 확인할 수 있습니다.

· One min read

좋은 날이 있으면 안 좋은 날도 있고, 안 좋은 날이 있으면 좋은 날도 있더라,

좋았던 날에는 그 느낌을 즐기되 겸손하자.

안좋았던 날에는 좋은 날이 올 차례라는 것에 설레여하자.

상대성 이론이 궁금해진 하루 ~

· 2 min read

pq_dump로 postgresql db 마이그레이션 하기.

상황 - 급하게 이슈를 해결하느라 prod db에만 데이터 변동이 생긴 상황, 추후 추가적인 이슈가 발생하지 않도록 코드 검토가 필요하며 dev 환경에서 검토를 위해 dev db와 prod db의 sync를 맞춰주어햐 한다.

해결 방안 - prod db를 dev db로 마이그레이션하자. 마이그레이션 방법은 dump 파일을 사용해 dev db에 덮어 씌운다. ( prod와 dev db의 데이터 구조와 데이터들이 꽤나 달라서 dump를 선택)

환경 - azure posgresql db

방법 - https://learn.microsoft.com/ko-kr/azure/postgresql/migrate/how-to-migrate-using-export-and-import

db migration - 현재 db 운영 환경에서 어떤이유로 인해 다른 운영환경으로 옮기는 것을 의미.

db dump - db의 모든 구조 및 데이터등을 포함하여 SQL문의 목록 형식으로 보관하는 것. 데이터베이스 손실 시 내용을 복원할 수 있도록 백업하는 용도로 가장 많이 활용됨.

· 2 min read

테스트 코드와 꼼꼼한 테스트의 필요성을 느꼈던 하루

현재 우리 팀은 신규 서비스를 담당하여 개발하고 있으며, 신규 서비스가 하나가 아닌 여러가지가 계속해서 생겨나고 있다. 그 중 하나의 서비스의 서버개발을 이번에 진행하게 되었는데, 시간이 여유롭지않았지만 구현할 때 충분히 고민하고 내가 짠 코드에 대한 충분한 이해를 가지고 구현했기에 문제가 없을 것이라 자신하며 출시를 진행하였다.

해당 서비스에 별도의 QA절차가 없었으며 코드를 작성한 나는 자만하며 테스트 및 검토를 꼼꼼히 하지 않았다.

그리고 이는 버그 발생으로 이어졌다.

오늘 낮잠을 자다가 부랴부랴 이슈를 해결하였고, 지금이라도 다시 꼼꼼히 검토하고 테스트 해봐야겠다는 생각이 들었던 하루다. (테스트 코드 작성까지 해보자!)

· 3 min read

그래프에 표시하기 위한 데이터리스트 반환 API 구현하기.

이용자가 추이를 확인할 수 있게 선 그래프를 표현하기 위한 데이터 리스트를 반환해주는 API를 구현하면서 여러 고민이 들었다.

  • 그래프는 누적 데이터를 보여준다.
  • 하루, 주, 월, 년 단위로 데이터 추이를 보여준다.
  • 각각 데이터는 (10분, 1시간, 8시간, 1일)의 간격을 가지며 화면에서 선 그래프에 마우스를 가져다 됬을 때 해당되는 데이터의 값을 보여준다.

그래프 표현 용 데이터에 대해 저장과 조회 두가지 관점에서 고민하였다.

  1. 저장의 관점에서, 그래프에 표시하기 위한 누적 데이터를 저장하며, 배치를 통해 매분 값을 저장한다.
  2. 조회의 관점에서, 누적 데이터가 매분 저장되어있기 때문에 별도의 계산은 필요하지 않으며, 데이터의 간격이 조건에 따라 10분, 1시간, 8시간, 1일이어야한다.

매 분마다 저장되어있는 테이블의 데이터에 대해서 특정 간격에 따라 데이터를 가져오기 위해 쿼리문에 조건을 추가하여 구현하였다. timestamp (type- datetime) 컬럼이 존재하며,

일(10분 간격) → 분 부분이 10으로 나누어 떨어지는 데이터들 주(1시간 간격) → 분 이 00인 데이터들 월(8시간 간격) → 분이 00이고 시간이 8로 나누어 떨어지는 데이터들 년(1일 간격) → 분, 시간이 00인 데이터들

로 쿼리문을 날려 구현하였다.

쿼리문 예시 - SELECT * FROM table_name tn WHERE tn.timestamp BETWEEN :beforeDate AND :nowDate AND EXTRACT(MINUTE FROM tn.timestamp) = 0 AND MOD(EXTRACT(HOUR FROM tn.timestamp), 8) = 0 ORDER BY tn.timestamp ASC;

EXTRACT는 날짜 함수로써 원하는 날짜 영역을 추출할 수 있다. MOD는 나누기 함수.

· 3 min read

쿼리문법과 orm 라이브러리로 해당 문법을 구현하는 방법 공부하자..!!

현재 골프 선수, 일정, 결과 데이터를 보관하고, 데이터 리스트를 커스터마이징하여 반환해주는 서버를 개발하고있다. (사내 NFT 프로젝트의 일환으로)

경기 결과, 선수의 랭킹 리스트등을 반환해 줄 때 데이터들의 정렬 기준이 있는데, (크게 랭킹 리스트 정렬 기준, 경기 결과 리스트의 정렬 기준) 데이터를 저장할 때는 정렬을 하고 저장하지 않으며 단 정렬값으로 사용될 데이터들을 보관한다. 그리고 조회할 때 order by, where, join을 적절히 활용하여 정렬된 기준으로 리스트를 조회해하는 식으로 구현하였다.

우선 쿼리문으로 작성을 하였는데, oder by의 정렬값으로 사용할 값들이 매핑된 다른 테이블의 값들도 있었으며, 정렬 시, 동점자를 발생시키지 않기 위해 기획적으로 굉장히 여러 정렬값이 존재하여 쿼리문을 작성하는데도 어려움이 많았다. (결국 실장님의 도움으로 해결하였으며 새로운 sql 함수들과 지식들을 알게되었다.)

쿼리문 작성이 끝나고 이제 서버에 typeorm으로 구현을 할 차례인데, 난관이었다. 진행을 하면서 쿼리문법과 orm 구현체 코드의 문법에 대해 평소에 잘 모르다보니 이런 정렬이 가능한지도 가늠이 안되고, 구현하는데까지 너무 많은 시간이 걸렸다. (subquery, leftjoin으로 다른 테이블을 조인하고 다른 테이블의 컬럼을 order by의 정렬값으로 사용하는 것, transaction 범위를 커스터마이징 하는 것 등)

결국 구현을 했지만 구현한 쿼리문보다 더 효율적인 쿼리를 작성할 수 있으려면 쿼리문법과 orm 라이브러리를 좀 더 깊이 있게 학습할 필요가 있다고 생각했던 하루다.

· One min read

오랜만에 김영한님의 자바 orm 표준 JPA프로그래밍 책을 꺼내서 코드를 따라치고, 서버를 실행했는데 오랜만에 봐서 반가운( 현재 회사에서 nest를 사용 중 )로고를 보여줬는데, 문제는 다음 로그를 보여주지 않았다.

2023-04-16.png

얼른 실행시켜서 확인하고 책을 다음장으로 넘어가고 싶은데 서버가 실행이 안된다.

알고보니 라이브러리의 버전 문제였다. 스프링 부트는 라이브러리의 의존성 버전을 자동으로 맞춰주는데 이 때 충돌이 발생한 것이다.

각 라이브러리의 버전 명시를 제외함으로써 해결.

· 3 min read

monorepo로 관리하면 어떤 장점이 있을까 ?

우리 팀은 모노 레포로 프로젝트들을 관리하고 있다.

이전 회사에서는 서비스마다 레포지토리를 분리하여 관리하고 있었기 때문에 처음 입사하고 모노레포를 clone 받아 코드 구조를 파악할 때 낯설었던 기억이 있었다.

나는 당연히 프로젝트 단위로 레포지토리를 관리하는 것이 CVS의 의도대로 코드를 관리하는 것이며 적합한 방법이라고 생각했는데, 이번에 monorepo로 작업하면서 직접 느끼고, 찾아보면서 다른 시각을 가지게 되었다.

monorepo(여러 프로젝트를 하나의 레포지토리로 관리)의 장점으로 제가 직접 느낀 부분은 모듈화가 간편하다는 점이었다, 여러 프로젝트에 사용할 만한 컴포넌트들을 모듈화해서 손쉽게 공유할 수 있는 것이 장점이라고 느껴졌다. (서로 다른 레포로 관리하더라도 라이브러리화해서 어떻게 공유할 수 있겠지만 고려해야될 부분과 불편한 부분이 많을 것이다. )그리고 여러 라이브러리들의 버전관리가 용이하다는 것도 큰 장점이라 여겨졌다.(특히 버전이 자주 바뀌고, 하위호환이 잘 안되는? Javascript 진영에서는 더욱이 큰 장점이라 생각들었다.)

그리고 한 팀에서 여러 서비스를 개발하고 관리하는 우리 팀의 특성상 monorepo를 사용하는 것이 작업의 효율성을 증대시키는데도 도움이 될 것이라 생각한다. (반대로 이전회사에서는 서비스 별로 팀이 나눠져 있었고, 각 팀 단위로 개발 규칙들이 있을 것이기 때문에 레포를 분리해서 관리하는 것이 편할 것이다.)

구글도 크롬과 안드로이드를 제외한 나머지 코드를 하나의 모노레포로 관리한다고 한다! 레포지토리 용량이 86TB이고, 해당 레포에 하루 커밋수가 40,000정도 된다고 한다. ㅋㅋ

https://qeunit.com/blog/how-google-does-monorepo/

https://dl.acm.org/doi/pdf/10.1145/2854146

· 3 min read

promise, await, async 란 ?!

async, await은 비동기 처리를 위한 문법입니다.

비동기 처리를 한다는 것은 비동기로 실행되는 코드를 순서가 보장되도록 처리한다는 의미로 이해했습니다.

(실제 프로세스가 동작하는 순서와 개발자가 작성한 코드의 순서의 싱크를 맞추는 처리 작업으로 이해했습니다.)

예를 들어 어떤 함수에 비동기로 실행되는 메소드가 있을 때, 순차적으로 수행되어야 원하는 로직의 결과가 나오는 경우, 순차 수행이되도록 보장하는 것입니다.

가령 아래와 같이 Dao를 찾아서 출력하는 메소드가 있을 때, 비동기 처리를 하지 않는다면,(findOne은 Promise객체를 반환하는 메소드.) findOne코드 부분의 처리가 if (dao)조건문 처리보다 늦어지게 되는 상황이 발생하는 경우, dao 변수 값은 undefined (혹은 null?)이 되어 실제로 dao가 DB에 있더라도 if 조건문은 실행되지 않게 될 것입니다.

function findDao() {
const dao = this.daoRepository.findOne({
where: where,
});

if (dao) {
console.log(dao);
}
}

아래와 같이 함수에 async 예약어를 붙이고 내부의 비동기 처리를 하고자하는 코드에 await을 붙이게 되면, 해당 코드는 비동기 처리가 되어 코드의 순서가 보장되게 됩니다. 즉, .findOne이 수행되고 이후 조건문이 수행되게 됩니다.

async function findDao() {
const dao = await this.daoRepository.findOne({
where: where,
});

if (dao) {
console.log(dao);
}
}

주의 사항 - 비동기 처리를 하고자하는 메소드는 Promise 객체 타입이어야 정상적으로 비동기 처리가 됩니다. 즉, 위의 예시에서 findOne메소드는 Promise 객체를 반환합니다.

· 2 min read

랭킹 리스트를 반환해주는 API 구현하기.

특정 포인트를 기준으로 랭킹 리스트를 반환해주는 API 구현이 필요한 상황.

랭킹 데이터를 어떻게 보관하고 반환해주는 것이 효율적일지 우선 고민해보았다.

결론적으로 기획단에서 정해질 랭킹이 갱신되는 주기가 짧냐, 길냐에 따라 두 가지 방법을 생각하였다.

만약 랭킹이 갱신되는 주기가 짧거나 실시간이라면 랭킹 관련 DB 함수를 사용해서 API 요청마다 계산 후 반환하는 것으로 계획하였으며, 랭킹과 관련된 함수는 일반적인 정렬 함수보다 효율적으로 특정 컬럼을 기준으로 레코드 순위를 정해주는 것으로 이해하였다. 만약 랭킹이 갱신되는 주기가 길다면, 갱신되는 시점에 랭킹을 계산해 DB에 저장하고, 그 사이에는 계산된 랭킹 순으로 데이터를 반환해주기만 하는 것이 효율적일 것이다.

결론적으로 내가 구현하는 서비스의 특성상 특정 경기가 종료될 때마다 랭킹이 갱신되며, 경기의 텀이 일주일정도 있기 떄문에 후자의 방법으로 구현하였다.

참고자료 : postgresql 랭킹 함수 , azure에서 지원하는 game 관련 서비스의 ranking system

· 4 min read

단위 테스트는 어느 정도까지 해야할까?

JPA 공부를 하다가 실습 테스트 코드를 작성중에 고민이 들었다.

아직 테스트코드 작성에 익숙하지 않고, 가장 베스트인 단위 테스트 코드가 무엇인지 고민해나가는 단계이다.

테스트 코드 작성을 익히기 위해 지난 회사 온보딩 기간에 인증서버 테스트코드 작성을 업무로 가져가면서 많은 검색과 고민을 반복했었는데 오랜만에 테스트코드를 작성하려니 새로 작성하는 기분이다.

entityManager를 사용해 간단하게 회원가입하는 로직을 테스트하려는 도중에 또 어느 정도까지 테스트 코드를 작성해주어야하는지 고민이 든다. (이전에도 같은 고민을 한 것 같기도 하다.)

단위테스트에 걸맞게 의존성을 최소화하기 위해 mocking을 해준다. repository가 mock객체이기 때문에 실제 db에 저장되지는 않을 것이며 그러면 저장된 값을 임의로 지정해주어 테스트해준다.

  • 그렇다면 repository 로직에 대한 단위 테스트가 추가되어야할 것이다.
  • 그리고 join 메소드는 반환값을 repository.save로 받은 값이 아닌 member.getId(member 객체는 join의 인자로 받는 값.)을 반환하도록 하는 것이 좀더 단위 테스트를 작성하기에 수월한 코드가 될것이다.

(왜냐하면 repository 반환값을 반환하도록 짜게되면, 단위테스트 시 실제 repostory는 null을 반환할 것이고(mock객체기 때문에)그러면 테스트 대상인 join 메소드의 반환값도 지정해주어야하는데 이렇게 반환되는 값들을 모두 지정해주는 것이 테스트의 의미를 더 퇴색시키는 느낌이라 생각했다.)

도메인 모델 패턴과 트랜잭션 스크립트 패턴

도메인 모델 패턴 - 비즈니스 로직 대부분을 엔티티에 작성하고 서비스 계층은 엔티티에 필요한 요청을 위임하는 역할의 형태로 객체지향의 특성을 적극 활용하는 형태.

트랜잭션 스크립트 패턴 - 엔티티에는 비즈니스 로직이 거의 없고 서비스 계청에서 대부분의 비즈니스 로직을 처리하는 형태.

그렇다면 둘 중에 베스트는 무엇일까 ? 혹은 두 패턴의 장단점은 무엇일까?

우선 내가 생각했을 때는 자주 사용되는 메소드의 경우 엔티티에 작성하는 것이 객체지향적이라고 생각이 든다. 다만 그렇지 않은 경우 비즈니스로직은 서비스 계층에 작성하여 엔티티와 서비스 레이어의 각 역할을 명확히 하는 것이 좋지않을까하는 생각이다.

· 4 min read

나는 현재 회사에서 혼자 개발하고 있다.

오늘은 내가 담당하고 있는 프로젝트가 사실은 나 혼자 개발하고 있는 프로젝트가 아니라는 깨달음을 준 사건을 기록하고자 한다.

문제는 어제 저녁에 발생했다. 쿠폰 기능 출시가 코 앞으로 다가왔고, 빠른 테스트 → 수정 → 반영의 반복이 스테이징 환경에서 이뤄지고 있는 상황이었다.

그런데 갑자기 스테이징 환경으로의 github action 배포과정에서 gradle build에서 실패를 반복해서 하는 것이다.

로그는 iamport-rest-client-java 라이브러리를 해결할 수 없다는 문구였다.

마침 또 아임포트 스테이징용 계정을 새로만들어 적용하는 시점에 발생한 에러였기에 뭔가 아임포트 관련해서 문제가 있으리라 짐작하였고, 라이브러리 버전이 최신 버전이 아니었기 때문에 해당 버전을 최신 버전으로 올렸더니 잘 배포가 되었다.

그러고 작업이 남아 집에서 다시 다른 브랜치(새로 만든 아임포트 계정을 반영하지 않은 브랜치)로 배포를 진행했는데 동일한 로그로 배포에 실패하는 것이다..!

뭔가 잘못됬음을 느꼈다. 이 후에 해당 브랜치도 아임포트 라이브러리의 버전을 변경해보았고 변경해도 실패하였다.

이후로 여러가지 원인분석을 하였는데, 결론은 상황에 관계없이 규칙적이지 않은 결과를 보였다. (배포가 실패하고 성공하는 기준이 규칙성이 전혀없음.)

오늘 아침에 기존에 같은 팀에 계셨던 사수께 양해를 구하고 허들을 요청했다. (젠틀하고 명석하신 분이시다.)

함께 원인을 파악하다보니 발견한 엄청난 사실.. iamport라이브러리는 jitpack이라는 서비스에서 관리되었고 해당 jitpack api 들이 다 다운 된 것이다.

또 공교롭게도 해당 서비스를 담당하는 개발자 분들은 휴가신 것 같았다.

아임포트 개발자분들께 해당 상황을 문의드렸고, 전 세계적으로 동일한 문제이며 해당 회사에서도 대응을 기다리고 있다는 답..

다른 팀의 개발자분의 조언으로 아임포트라이브러리를 내장 라이브러리로 프로젝트에 삽입하고 연결하여 해결은 하였지만, 정말 벙찐 순간이었다.

이번 사건을 계기로 내가 짠 코드에 옆에는 없지만 많은 개발자분들이 함께 작업하고 있다는 것을 느꼈다.

동지들

2022-12-23-3.png

홀리

2022-12-23-2.png

다행히 잘 해결됨ㅎㅎ

2022-12-23-1.png

· 5 min read

최근 사내에서 신규로 출시한 웹서비스의 유지 보수 및 기능 고도화 작업을 진행하고 있다.

프론트와 백엔드 개발은 혼자서 담당하고 인프라 쪽만 데브옵스 개발자 분들이 도와주고 계시는데, 프론트와 백엔드를 모두 담당하면서 얻게 된 깨달음이 있어 기록을 남긴다.

사이드 프로젝트로 간단한 앱을 만드는 프로젝트를 6개월 째 진행하고 있는데, 해당 프로젝트에서는 백엔드만 담당하여 API의 응답 값이 프론트에서 어떻게 다뤄지는지에 대한 고민을 해보지 못했다. 단순히 프론트 개발자분과 약속을 통해, 응답 데이터에 status, data, message 정보를 담아 보내기로(badRequest, notfound, internal server error 등 모든 예외에 대한 응답을! ) 약속만 하고 그냥 그 약속을 지키기만 했다.

그러다가 회사 업무를 하면서, 한가지 이슈를 통해 응답 처리에 대한 고민을 하게 되었다. 이슈는 다음과 같다.

쿠폰을 발급하는 기능을 구현하였고, 해당 유저가 쿠폰을 이미 발급받은 경우(디비에 발급받은 쿠폰이 있는지 조회) 커스텀하게 만든 Response 객체에 data로 badRequest를 담아서 리턴하도록 API를 만들었는데(약간은 다르지만 사이드 프로젝트에서 하던 방식과 비슷하게) 프론트단에서 실행해보면 중복으로 발급해도 정상적으로 동작을 하는 것 처럼 보이는 것이다.

크롬 개발자 모드로 들어가보니 서버는 응답으로 badrequest를 데이터로 반환하였지만 응답 상태는 200(초록불)이었던 것이다. 또한 프론트 코드에서는 try,catch문으로 로직이 작성되었기 때문에 해당 응답은 정상처리가 되어 별도의 다른 처리를 하지 않았던 것.

이 때 선택지는 두가지임을 깨달았다.

첫번째로 try,catch로 예외를(여기서는 쿠폰이 이중발급되는 상황) 처리하는 것이 아닌 응답 데이터 값으로 조건문을 통해 처리하는 것.

두번째로 서버에서 응답 객체를 반환하는 것이 아닌, 예외를 발생(throw)시키고 프론트엔드에서는 try catch를 통해 해당 예외를 처리하는 것.

그리고 try catch와 예외발생으로 처리하는 것이 해당 기능을 잘 사용하는 것이고, 또 로직적으로 깔끔하다는 것을 꺠달았다.

또한 이전에 서버 관점에서만 생각할 때, 예외를 발생시키면 프론트에서 어떻게 처리하는지 추측이 안되서 예외를 최대한 발생안시키려고( js에도 try catch가 당연히 있음을 알았음에도, 프론트의 코드를 볼일이 없었으니 어떻게 처리하는지를 몰랐다.) 했었는데 이 때 추측하지 못했던 것들을 깨닫게 되었다.

처음 사내에서 해당 프로젝트를 담당하면서 고민이 많았는데 (프론트, 백엔드 두가지 모두 맡게되어) 두 가지를 모두 담당함으로써 얕지만 새로 얻는 지식들이 있음을 느끼고 있다.

· 3 min read

AWS LightSail 실습 세미나 수강하기.

AWS에서는 아주 아주 많은 세미나 및 교육 프로그램을 제공한다.

구체적인 교육 프로그램 구성이나 절차에 대해 알지는 못하지만, 체감적으로 한달에 1-2번은 나에게 필요한 교육 프로그램이 열리는 것 같다. (메일, 광고를 통해 주로 교육이 열린다는 것을 알게 된다.)

이전 부터 신청은 2,3번 했었지만, 일반적으로 낮에 세미나가 열리거나 몇일에 걸쳐 진행이 되었고, 당시에는 나의 주된 관심사가 아니었기에 결국 한번도 들어보지 못했었다.

요즘 온라인 스터디에서 진행하는 프로젝트가 배포단계를 거치고 있는데, lightsail로 배포를 계획하게되어 겸사겸사 세미나를 신청하게 되었다. (신청하면서 몇번의 고민이 있었음.. 또 안들을 것 같아서)

결국 AWS 첫 세미나를 참석하게 되었고, lightsail을 이용해 wordpress 블로그를 배포하는 절차를 통해 lightsail을 학습할 수 있었다.

또 부하테스트를 진행할 수 있다는 것을 새로 알게 되었고 진행해보아야겠다는 생각을 하게되었다.

1시간 30분동안 진행하며, 질의도 가능하기에 세미나에서 교육하는 기술에 대해 처음 접하시는 분이나 이미 사용하고 있지만 평소 궁금한 점이 있었던 분들도 들으면서 많은 것을 얻어 갈 수 있는 시간이라 생각된다.

관심있는 교육이 열리면 하나씩 참여해봐야지!!

· 4 min read

사내에서 담당 개발 중인 채용 사이트의 admin페이지 계정이 평문으로 DB에 저장되어있는 것을 확인하였다.

지난주 면접(N사)에서 암호화와 관련된 질문을 받았던 적이 있어서 채용 사이트 어드민 계정 암호화를 진행하면서 정리해보자.

두 가지 암호화 기법

Hash와 Encryption

두 가지 모두 암호화 기법이지만 Hash는 단방향 암호화 기법이며 Encryption은 양방향 암호화 기법이다.

따라서 Hash는 암호화가 된 값을 다시 원래의 평문으로 돌리는 것이 불가능하지만 Encryption은 가능하다.

Salt

평문에 임의의 값들을 덧붙여 해시함수에 넣는 것을 말한다. (패스워드에 소금을 치는 것.)

이렇게 하면 값의 길이가 길어져 Rainbow Table(해시 함수를 사용하여 만들 수 있는 값들을 왕창 저장한 표, db)를 이용해도 쉽게 알아내지 못하도록 할 수 있다.

프로젝트에 적용할 암호화 기법

나는 spring security 프로젝트의 BycryptPasswordEncoder를 사용하고자한다.

사용한 이유는 다음과 같다.

  1. 해당 클래스는 Bcrypt 해시 함수를 사용하며 bcrypt 해시 함수는 패스워드 암호화를 위해 만들어진 아주 강력한 해시 알고리즘임.
  2. salt를 사용함.

로그인 시 db의 패스워드와 비교하는 로직은 다음과 같다.

  1. 입력받은 password를 암호화.
  2. 암호화한 값과 DB의 암호화된 값을 비교.

→ DB의 암호화된 패스워드를 복호화하여 입력받은 패스워드와 비교할 수도 있지 않을까?라는 생각이 들 수 있지만 해시알고리즘을 사용하기 때문에 한번 암호화된 값은 복호화가 불가능하다. 따라서 동일한 값을 해시 함수에 넣을 경우 항상 동일한 해시 값을 리턴하는 것을 이용해 인증을 한다.

→임의의 salt값이 붙어져서 암호화 되기 때문에 같은 패스워드라도 암호화된 값은 매번 달라질 수 있다. 따라서 equals가 아닌 클래스에서 지원하는 matches 메소드를 사용하여 비교한다.

해시 함수는 완벽하지 않다.

충돌이 발생할 수 있으며 충돌이 발생한다는 것은 다른 값에 대해 동일한 해시값이 발생할 수도 있다는 것이다. 따라서 동일한 해시값이 적게 발생하는 해시 함수가 좋은 함수이다.

또한 암호화가 아닌 검색(조회) 속도 향상을 위해 만들어진 것이기 때문에 부르트포스 방법으로 임의의 값을 넣어 대면 해킹을 당할 수도 있다.

참고자료

· 7 min read

주말마다 감사하게도 온라인 스터디를 진행하고 있다.

이번 스터디에서 기본적이지만 평소에 생각해보지 않았던 것이 생겨 글로 당시의 깨달음을 남긴다.

구현된 API중에 GET Method로 정보를 요청하는 API가 있다. 해당 API는 요청 파라미터로 4가지의 파라미터를 필요로한다.

나는 문득 생각이 들었고 해당 코드에 리뷰를 남겼다.

“4가지 값들을 파라미터로 받는 이유가 있을까요?”

해당 리뷰를 남긴 이유는 경로 파라미터로 4가지를 넘겨주는 것보다는 body로 넘겨주는 것이 낫지 않을까? 하는 생각이 들었기 때문이다.

여기서 팀원분들의 의견을 듣다가 알게된, 내가 잊고 있었던 개념이 있다.

관례적으로 GET Method는 Body를 사용하지 않는다는 것이다.

(웹 개발자로 진로를 준비하던 오래되지 않은 과거에 이러한 개념을 공부했던 기억이 났다..ㅋㅋ

POST와 GET의 차이는 무엇인가요 ? → POST는 body에 데이터를 담고, GET은 uri에 담는다. GET은 url에 데이터가 바로 드러나며 POST는 바로 드러나지는 않지만 Client가 확인할 수 있기에 보안상 데이터 암호화가 필요하면 둘다 해야한다..와 같은 기억이 새록새록)

음..그런데 GET 요청, 즉 어떠한 데이터를 조회하기 위한 목적이지만 body에 값을 담아주는 것이 더 적합하거나 효율적인 상황이 있을 수 있지 않을까 ?

stackoverflow에서 GET메소드에 body를 사용하는 것에 대해 많은 사람들이 함께 고민을한 흔적들을 볼 수 있었다.

10-09-1.png

질문글을 요약하면 다음과 같다.

‘어플리케이션을 위한 새로운 RestFul 웹서비스를 만들고 있다. 특정 Entity들을 얻기위한 GET동작 API를 만들고 있으며 나는 GET요청을 위해 사용되는 parameter를 reqeust body에 담아 요청할 수 있게 하고 싶다. HTTP/1.1에서는 이를 금지하고 있지 않는 것으로 보인다. 이러한 방법(body에 parameter를 담는 것)은 더욱 명확한 요청을 할 수 있게 하고 복잡한 XML 요청을 쉽게 할 것이다.’

10-09-2.png

채택된 답변을 내용을 보면 간략하게 다음과 같다.

‘HTTP/1.1 문서를 보면 “그러한 방법을 권장하지 않는다.”, “GET 메소드는 “URI를 통한 요청”으로 정보를 얻는 것을 의미한다.”와 같은 내용이 있다. 즉 GET으로 바디에 값을 담아 요청을 하는 것이 가능은 하지만 바디의 값은 어떠한 의미도 갖지 않는다.’

그리고 해당 답변에 업데이트된 내용이 있다. 2014년도 HTTP/1.1 문서에서는 앞서 말한 문서의 내용이 삭제 되었고, 요청 데이터를 담는 것은 메소드의 의미론과는 독립적인 개념이라는 내용으로 대체되었다는 내용이다.

그리고 부가적인 코멘트의 내용들 중 elasticsearch는 GET요청시 body에 data를 담아 전달하는 대표적인 프로덕트라는 내용이 있었다.

elasticsearch Getting Started 문서를 보면 다음과 같은 내용을 확인할 수 있다.

10-09-3.png

요약 : RFC 문서에 따로 GET메소드에 body를 사용하는 것에 대한 제약조건이 없기 때문에 사용할 수 없는 것은 아니며, elasticseach 관리자는 body를 사용하더라도 의미적으로 정보를 조회하기 때문에 GET 메소드를 사용하는 것을 선호한다. 다만 언어적으로 GET메소드에 body를 담는 것을 허용하지 않는 언어들이 있기 때문에 POST또한 당연히 지원을 한다. 가령 javascript의 http 라이브러리는 해당 방식을 허용하지 않는다.

10-09-4.png

10-09-5.png

Spring에서 테스트를 해보니 GET 요청 시 body에 값을 담아보내고 @RequestBody로 값을 받을 경우 별도의 예외처리가 되어있지 않다.

stackoverflow 해당 내용을 보면 SpringMVC에서도 과거에는 해당 방법을 허용하지 않았던 것 같다. v5.3.6(spring)이후로 GET 요청에 body에 데이터를 담아 보내는 것을 허용했다는 코멘트는 확일할 수 있다.

결론

2014년도를 기점으로 HTTP 문서에서 메소드정의와 데이터 요청 처리 방법은 별개의 개념이라는 것을 명시하였다.

elasticsearch는 GET 메소드에 body를 사용하는 것을 권장한다.

하지만 소위 말하는 RestFul하게, HTTP Spec을 준수하여(지금은 HTTP Spec에 관련 내용은 삭제되었지만) 예전부터 관례적으로 룰을 정해 사용해왔기 때문에 이러한 설계 규칙을 벗어나는 것은 함께하는 공동체의 동의 및 부수적인 고민들이 필요할 것이다.

· 3 min read

일주일간의 온보딩 기간 후에 두가지 태스크를 정하여 두달간 진행하게 되었다.

그 중 하나의 태스크는 바로 채용 사이트 담당 개발!

이미 1차적으로 만들어져있는 상황이며 추가적인 요구사항에 맞춰 공지사항과 몇가지 수정, 전반적인 코드리팩토링을 진행하게 되었다.

(외주로 급하게 만든 프로젝트이기에 이후에도 안정적인 유지 관리를 위해 리팩토링 하기로 결정.)

기존에 배포되어있는 환경(ec2)에서 eks로의 프로젝트 이전이 필요했으며 프론트 작업도 필요했기에 꽤나 여러분과의 커뮤니케이션이 필요한 작업이었다.

devops 엔지니어와의 소통, 프론트 엔지니어와의 소통, 디자이너와의 소통, 인수인계 해주신 백엔드 개발자와의 소통..!

결과적으로 2주간의 작업 끝에 오늘 배포를 진행하였으며 공지사항의 이미지 업로드 기능을 제외한 채 배포를 하게 되었고 이미지 업로드 기능은 차주 수요일 배포로 연장하게 되었다..

다행히 급한 요구사항이 아니어서 늦출 수 있었지, 만약 급한 요구사항이었다면 굉장히 난감한 상황이 생길 뻔했다.

이렇게 약간은(?) 아쉬운 결과가 나오게된 원인을 생각해 봤다.

그 원인은 바로 바로 여러명의 인원이서 함께 작업을 진행할 때, 결코 나의 계획대로 진행되지는 않는다는 것을 몰랐고 그래서 계획에 차질이 생겼을 때 대처할 만한 준비를 하지 않았다는 것이다.

다양한 사람들이 각자의 상황이 존재하기 때문에 당연하게도 내가 계획한대로 진행되기에는 변수가 너무 많은 것이다.

다음 부터는 항상 최악의 상황, 혹은 다양한 변수를 고려하여 업무를 진행할 필요가 있다는 것을 깨달은 하루였다.

· 4 min read

@Setter를 지양하는 것이 좋은 것인가 ?

오늘 코드 리뷰를 받으면서 @Data와 @Setter를 지양하는 방향으로의 수정을 제안 받았다.

(이 때 @Setter는 객체 초기화가 아닌 필드 수정 시 사용되는 상황입니다.)

해당 리뷰를 통해 언급한 두가지 어노테이션을 지양해야되는 이유에 대해 고민해보았다.

@Setter를 클래스에 적용하면 해당 클래스의 필드들을 수정하는 메소드가 모두 생성이 된다. 수정 가능성이 없는 필드들에 대해서도 수정 메소드가 생기는 것은 원하지 않는 동작이 발생할 가능성을 만들어낸다.

즉, 객체의 일관성을 유지하기 어렵게 만든다.

필드에 @Setter를 적용하면은?? 그러면 위 문제는 해결된다고 생각한다. 지양해야되는 다른 이유가 또 무엇이 있을까??

리뷰를 주신 분께서 앞전에 다른 레포에서 의견이 오고갔던 @Setter사용 관련 코멘트들을 보여주셨고 해당 의견에는 이러한 내용도 있었다.

“@Setter를 사용하는 것이 아닌 메소드를 따로 구현하면 메소드 명을 통해 해당 값을 변경하는 의도를 좀 더 명확히 표현할 수 있습니다.”

즉, @Setter 수정 메소드를 사용함으로써 코드 가독성을 높이고 유지보수측면에서 이점을 가져갈 수 있다.


별개로 @Setter를 지양하는 것에 대해 구글링을 해보니, 객체를 생성하여 값들을 초기화하는 상황에서 @Setter를 지양해야한다는 글들이 많아 해당 내용도 간략히 정리해보자.

언급한 상황에서 @Setter를 사용하면 두가지 문제점이 있다.

  1. 객체의 일관성 유지 어려움.
  2. 변화 의도를 파악하기 어려움.

수정할 때 @Setter를 사용하는 상황과 비슷한 문제점이다.

이를 해결하기 위한 방법으로는

  1. 생성자 사용
  2. @Builder 사용(권장)

이 있다.

생성자를 사용하면 인자들이 다양하게 들어오는 것에 대해 일일이 생성자들을 만들어주어야하며 어떤 필드에 어떤 값이 들어가는지 확인이 어렵기 때문에 가독성이 떨어지는 문제가 있다.

빌더 패턴을 사용하면 이러한 문제들을 해결할 수 있다!

· 2 min read

지속 가능한 소프트웨어 설계 패턴: 포트와 어댑터 아키텍처 적용하기

Hexagonal Architecture

회사에 입사한지 일주일정도가 다 되가는 시점..! 노션을 이곳저곳 기웃거리던 중에 흥미로운 글을 발견했다.

아키텍쳐!

입사 면접 당시 아키텍쳐에 대한 책을 읽어보거나 공부를 해본적이 있냐는 질문을 받은 기억이 났다. 또한 3개월간 인턴 근무를 했던 이전 회사에서 프로젝트를 설계할 때, 사수에게 어떤 아키텍쳐를 도입할 계획인지에 대해 질문을 받았던 기억이 났다.

점점 더 큰 규모의 프로젝트를 접할 기회가 생기면서 DDD, TDD등과 함께 아키텍쳐 설계에 대해 공부해야할 필요성을 느끼고 있던 와중에 새로운 아키텍쳐 이름을 발견했다!

헥사고날, 육각형 설계 방식은 어떤 것일까

· 9 min read

파이썬에서는 자바와 같이 Static을 남용하는 것을 지양하는 이슈가 없는 것인가 ?!

Spring으로 서버 개발을 계속해오다가 최근 Django(DRF), FastAPI로 서버개발을 하는 기회가 생기게 되었다. 새로운 프레임웤으로 개발을 하면서 낯선 부분들이 많았기에 기초적인 개념들부터 무작정 학습을 하다가, 최근 이제 View단과 Serivce단을 분리할만할 정도로 로직이 구체화되어 분리를 하던 중에 문득 이런 의문점이 들었다.

파이썬에서는 자바와 같이 static을 남용하는 것을 지양하는 이슈가 없는 것인가 ?!

레이어를 분리하면서 python에서 다른 레이어(클래스)의 메소드를 클래스 네임으로 바로 참조하여 사용하는 코드들(@classmethod, @staticmethod를 사용)을 그냥 별 생각없이 따라 사용하다가, 어라? 뭔가 찝찝하지 왜 ?(이렇게 간편하다고 ?)라는 기분이 들었다.

그리고 Spring 프레임웤에서는 어떻게 했더라 ? 생각해보니, 자바에서도 static 키워드를 사용하면 메소드와 변수를 static으로 선언할 수 있고 클래스 네임으로 바로 참조가 가능하지만 스프링에서는 그렇게 하지않는다. 그리고 객체들을 프레임웤에서 직접 관리해 주지만, 해당 객체(빈)을 등록하고 주입하기위해서는 개발자가 어느정도 객체 관리를 고려하여 어노테이션 및 코드를 짜주어야한다.

그런데 파이썬에서는 static을 남용해도 전혀 문제가 되지 않는 것인가 ?!

자바에서 static을 지양하는 이유는 다양한데, 핵심적으로는 다음과 같은 이유가 있다.

  1. Java에서는 static은 프로그램이 실행 시에 data영역에 생성되어서 많이 사용할 경우 메모리를 낭비하게되는 이슈가 있음.
  2. static이 객체지향 관점에 반하는 개념이라는 의견.

(static 사용과 관련한 많은 의견들이 있는 스택오버플로우의 한 게시글을 읽어보면 더욱 구체적으로 이유를 파악 할 수 있따.)

Django와 같은 큰 규모의 프레임워크에서 static 객체를 많이 사용한다면(@classmethod, @staticmethod), static을 남용해도 파이썬에서는 문제가 되지 않을 거라는 것을 짐작할 수는 있다. 하지만 그 이유는 무엇일까?

자바에서 static을 지양하는 대표적 이유에 근거하여 python에서는 문제가 없는 이유에 대해 몇가지 추측을 해보았다.

추측 1. @classmethod, @staticmethod를 사용해도 자바에서와 달리 파이썬에서는 data영역에 static 객체가 생성되지 않는다. ( 대신 heap영역? )

즉, 자바에서의 static 메소드처럼 @classmethod, @staticmethod을 사용하면 클래스 네임으로 접근이 가능하지만, 메모리상에서는 static이 아니다.

추측 2. 파이썬은 하이 레벨 언어로서 메모리 관리에 도가 터 있기 때문에 코드 단에서 메모리와 관련된 고려해야될 이슈가 없다.

위의 추측들을 해결하기 위해서는 Python이 어떻게 메모리를 관리하는지를 이해할 필요가 있을 것 같다.

특징 1.

우선 프로그램이 실행되면, OS는 프로세스에게 정해진 Memory를 할당해주고 프로세스는 할당받은 Memory를 자신의 방식에 맞게 영역을 나누어 활용합니다.

“메모리는 컴퓨팅 시스템에서 중요한 구조이자 프로세스의 코어한 정보들이 모여있는 공간입니다. 중요성도 높고, 휘발될 여지가 있는 만큼 대부분의 프로그래밍 언어는 내장된 memory manager를 구성하여 둡니다. Python의 경우 VMM이 할당해준 빈 페이지들은 Python의 memory manager가 권한을 가지고 있습니다.

Python은 기본적으로 할당된 Memory 공간을 4개의 종류로 구분하여 관리합니다.

HEAP / STACK / Static&Global / CODE

HEAP, STACK 이 두가지 영역은 Dynamic memory allocation 과 static memory allocation을 위해 존재합니다. 이 두 영역의 메모리 할당 방식과 작동 원리는 Generator와 함께 이해하는 것이 좋습니다. 이를 위해 다음 글에서 자세히 다뤄보겠습니다.

Static&Global 영역은 전역 변수 등을 다루기 위해 할당되는 공간입니다. Code 영역은 instruction 들이 보관되는 영역입니다. 프로세스가 각 명령줄을 보관하는 목적으로 사용하는 공간입니다.

Python과 관련하여 메모리 관련 이해도가 필요한 부분은 heap 과 stack 부분인데, 이 부분에 대한 이해도가 높을 수록 성능과 효율성을 고려한 코드가 나오는 것 같습니다.” [원문 - https://day-by-day.kr/python-memory/]

위의 글에서 파이썬은 heap 영역과 stack영역을 핵심적으로 사용하며 static memory allocation (정적 메소드 생성과 같은)도 heap, stack영역을 사용하는 것을 알 수 있다.

특징 2.

또한 파이썬은 모든 것이 ‘객체'로 저장된다. 예를 들어 c언어에서는 x=10이라고 변수를 할당하면 메모리에 해당 값이 바로 저장되지만, 파이썬에서는 int라는 object를 만들어 변수 x가 해당 객체(10이 담겨있는 int 객체)를 가리키는 형태로 저장된다고 한다.

특징 3.

파이썬은 개발자가 직접 동적 할당을 할 수 있는 기능이없다.(ex c의 malloc) 파이썬은 자동으로 메로리를 관리해주는 언어이기 때문. python memory manager는 자동으로 포인터를 움직여서 메모리 할당범위를 조절해주며, 동적 관리를 한다. 또한 운영체제와 소통하면서 manager가 알아서 메모리를 관리함으로써 OS의 부담을 줄여주는 언어다.

참고자료 - https://woochan-autobiography.tistory.com/867

결론적으로 파이썬은 정적 메소드에 대해 data영역을 사용하지 않으며 메모리 관리에 우수한 성능을 가지고 있기 때문에 메모리와 관련하여 코드단에서 고려해야될 요소가 없다고 판단함.

다만 정확히 @staticmethod, @classmethod데코레이터를 붙인 클래스의 메소드가 어떻게 메모리에 할당되는 지에 대해 알면 더욱 정확한 결론을 얻을 수 있을 것이라 생각한다. 이를 위해서는 python memory manager에 대한 공부가 필요함!

파이썬 memory management에 관한 공식 문서

· 3 min read

좋은 분들과 진행하고 있는 디자인 패턴 스터디가 장장 3달을 이라는 시간이 흘러 마지막 주가 되었다.

마지막으로 정리를 담당하였는데, 정리를 위해 스터디한 내용을 다시 읽어보니 내가 이걸 공부했었나 싶은 놀라운 상황이..

언젠가 써먹을 때가 있겠지 싶어 그래도 개념들을 다시 한번 정리해보자.

사실 디자인 패턴이라는 개념에 어떤 식으로 접근을 해야할지 스터디 마지막 주까지도 감이 잘 오지 않는다.

그래서 현직자의 시선을 빌려보고자 여러 개발자분들의 블로그에 디자인 패턴에 대한 내용이 있는지 찾아보다가 현재 인프런 CTO를 맞고 게신 이동욱님의 블로그의 포스트로 들어가게 되었다.

자바지기(박재성)님의 세미나 내용 정리 글이었다.

삶을 훌륭하게 가꾸어주는 것은 행복감이 아니라 깊이 빠져드는 몰입이다.

좋은 글귀와 함께, 스터디의 막바지에 와서도 디자인 패턴에 대한 감이 잘 오지 않는 내가 정상이라는 것을 말해주는 문장이 있다.

  • 디자인 패턴을 공부해도 어디에 쓸지 모르는 경우가 많다.
  • 리팩토링을 끊임없이 하다가 디자인패턴을 보면 더 큰 깨달음을 얻지만, 반대로하게 되면 스트레스만 늘수도 있다.

맞다. 사실 지금의 나한테 디자인 패턴은 반드시 필요한 개념은 아닐 수 있다.

그렇다면 디자인 패턴을 공부하면서 얻을 수 있었던 경험은 무엇인가 ?

  • 자바 언어를 공부할 때, 알고리즘 문제를 풀거나 강의를 들으며 문법적인 것을 공부를 했다면 디자인 패턴을 공부하며 예시들을 보면서 순수 자바언어로 짜는 코드들에 대한 시야가 확장 됨.
  • 코드를 설명하는 능력의 부족함, 개선하고자하는 의지를 얻음.