Article - 깊게 탐구하기/시간 기록, 관리 서비스 Pinit 12

[Pinit] 도메인 이벤트로 소통하기 - gRPC

현재 시스템 구조현재 요구사항에는 일정 시작 시간에 푸쉬 알림을 보내야 한다는 요구사항이 있다. 하지만, 이미 일정을 진행중인/종료한 사람에게 해당 푸쉬알림이 발송될 경우, 이는 UX 측면에서 사용자의 집중력을 해칠 우려가 있기 때문에, 일정이 시작된 후에는 푸쉬 알림이 발송되지 않아야 한다. 내부 상태 변화(Schedule의 "시작됨" 상태 변화와 "취소됨" 상태 변화)를 외부 시스템이 알아야 하는 경우가 발생했다. 이렇다면, 일정 시작 시간에 발송되는 푸쉬 알림은 일정이 "시작되지 않음" 상태일 때에만 발송되어야 한다.즉, 일정의 "시작되지 않음" 관련 상태/상태 변화가 외부 시스템에 전달되어야 한다. 이에 대해 나는 다음과 같은 해결책을 고려했다.도메인 이벤트로 해결하기상태 변화 알리기 - 일정 시..

[Pinit] 푸시 알림 발송하기

FCM 토큰으로 푸시알림을 발행하려고 하자, FCM 토큰이 UNREGISTERED되어 있다는 응답을 받았다.공식 문서를 참고한 결과, FCM 토큰이 만료되었다는 의미라는 것을 알게 되었다. 토큰이 예상하지 못한 방식으로 만료될 수 있다는 것을 알게된 나는 토큰의 만료 관리 정책을 고려해야 한다는 걸 알 수 있었다.하지만, 일단 지금의 목표는 알림 발송이니, 알림 안오는 버그부터 해결하자.그렇게 만료된 토큰을 삭제하고 다시 시도해본 결과, 에러 메시지가 달라졌다.이번에는 Invalid Argument 문제를 마주했다. GPT에게 자주 발생하는 Invalid Argument 에러에 대해 물어보니 다음과 같이 답변해주었다.진짜 FCM 등록 토큰을 넣었나? 엉뚱한거 잘못 넣은 거 아닌가?토큰 문자열이 손상된 ..

[Pinit] 푸시 알림 Exactly-Once 구현하기

백엔드 서버 관점에서 일반적인 이벤트 발행의 Best Practice를 생각해보자.새로운 방법의 발견이 아닌, 일반적인 방법을 정리 후 적용하는 게시글이다. 트랜잭셔널 아웃박스 패턴 (Transactional Outbox Pattern)프로듀서 -> MQ 사이의 1회 이상 전송을 보장하는 매커니즘(패턴)이다.DB의 트랜잭션은 사용자의 요청과 DB 상태 변경을 원자적으로 묶어준다. 하지만, DB 상태 변경과 이벤트 기록, 그리고 이벤트 발행까지는 원자적으로 묶이지 않는다.트랜잭셔널 아웃박스 패턴은 DB 상태 변경과 이벤트 기록을 트랜잭션으로 묶는다.하지만, 트랜잭션 아웃박스 패턴은 1회 이상 발행 보장이지, 1회 발행 보장은 아니다.즉, 중복 발행 가능성이 있다.발행 후 해당 메시지를 발행함을 표기하지 못..

[Pinit] 푸쉬 알림 발송해보기 - 직접 Web Push 사용

웹 푸쉬 알림은 표준 Web Push 프로토콜을 따르며, VAPID 인증 방식을 사용하여 브라우저에 푸쉬 알림을 전송한다.이때 VAPID(Voluntary Application Server Identification)는 애플리케이션 서버가 푸쉬 서비스에 자신을 식별할 수 있도록 도와주는 메커니즘이다.VAPID는 그 자체로 푸쉬 서버에게 자신을 알리는 ID가 됨과 동시에, VAPID는 공개 키 암호화 방식을 사용하여 서버가 푸쉬 요청을 보낼 때 자신을 증명할 수 있게 한다.Web Push 구현 목표브라우저에서Notification 권한 요청pushManager.subscribe() 로 endpoint, keys.p256dh, keys.auth 획득서버에서VAPID 키 쌍 생성 및 관리payload 암호화,..

[Pinit] AsyncAPI - EDA를 향한 이벤트 교환 계약 문서 작성

추천 이전 글: Rabbit MQ의 이름 짓기 [RabbitMQ] Rabbit MQ의 이름 짓기개발자에게 가장 중요한 역량은 "이름 짓기"이다. 이름을 잘 지어둬야 나중에 읽을 때 빠르고 헷갈리지 않게 읽을 수 있기 때문인데, RabbitMQ 사용 시에는 이름을 지어야 할 부분이 세 군데나 있다dev.go-gradually.me Rabbit MQ를 이용한 이벤트 송/수신 방식을 설계하려면, 이 또한 계약을 설계해야 한다.프로토콜, API, 메시지 포맷 등을 설계하는 것은 성능 최적화 이상 중요한 백엔드 개발자의 매우 중요한 역할 중 하나이다.MSA 에서 이벤트 기반 메시징을 설계할 때, 다음과 같은 사항들을 고려해야 한다.프로듀서 주체Exchange nameRouting key컨슈머 주체Queue nam..

[Pinit] JWT 기반 OIDC 로그인/인증 구현 - OIDC 붙이기

이번 글은 이전 글의 다음 단계인 2, 3단계에 대해 다룹니다.코드는 다음 링크를 통해 확인 가능합니다. GitHub - GoGradually/pinit-authContribute to GoGradually/pinit-auth development by creating an account on GitHub.github.com 첫 MSA 도입, 한번에 다 만들려고 하면 복잡하다.각각 독립적인 기술인 만큼, JWT + OIDC 조합 두 단계로 나눠서 접근하자.목차내 JWT 인증 흐름 먼저 만들기그다음 OIDC 로그인 붙여서 “로그인 수단”만 확장하기회원가입 이벤트 발행 + be 서버에 공개 키 공유해서 인증 연동하기2. OIDC (외부 로그인) 붙이기(0) OIDC의 이해User-Browser사용자가 브라우..

[Pinit] JWT 기반 OIDC 로그인/인증 구현 - JWT 구현

코드는 다음 링크를 통해 확인 가능합니다. GitHub - GoGradually/pinit-authContribute to GoGradually/pinit-auth development by creating an account on GitHub.github.com 첫 MSA 도입, 한번에 다 만들려고 하면 복잡하다.각각 독립적인 기술인 만큼, JWT + OIDC 조합 두 단계로 나눠서 접근하자.목차내 JWT 인증 흐름 먼저 만들기그다음 OIDC 로그인 붙여서 “로그인 수단”만 확장하기회원가입 이벤트 발행 + be 서버에 공개 키 공유해서 인증 연동하기 이번 글에서는 0~1번 내용에 대해 다룬다. 0. 큰 그림 결정하기아키텍처 결정pinit-auth = 인증/토큰 발급 서버pinit-be = 일정/통계..

[Pinit] 통계 갱신의 누락 문제

핀잇 백엔드의 핵심 비즈니스 로직의 구현을 마치고, 간단하게 FE를 구현해 사용해보는 도중 통계가 정상적으로 기록되지 않는 문제를 마주했다. 먼저 문제가 생긴 애플리케이션 로직을 찾아보았다.orElseGet으로 새로운 통계 객체 생성 후 save() 를 호출해 저장을 수행하고 있었는데, 이 동작에 문제가 생긴 것처럼 보였다.먼저, 이벤트가 정상적으로 발행되지 않거나, 이에 대한 구독이 이루어지지 않았는지가 의심스러웠다. 그래서 다음과 같이 중단점을 걸고 디버깅을 수행했다.하지만 그 결과는 아래와 같이 중단점에 잘 도착하는 모습을 확인할 수 있었다.또한 값 변경도 잘 되는 것을 확인할 수 있었다.이벤트의 발행/구독에 대한 의심을 걷어낸 나는, 그 다음으로 이루어지는 작업인 SQL에 관심이 생겼다.그렇게 s..

[Pinit] HTTP Patch의 구현 방식

Get과 Post는 구현 방식이 비교적 단순하지만, Patch의 경우 몇가지 고려해야 할 점이 있다.여기서 외부 라이브러리 사용은 자제하고 싶었다.외부 라이브러리의 변경으로 인한 버그와 같은 문제는 피하고 싶었다.그렇다면 이제 Patch를 구현하기 위해, Patch의 요구사항을 살펴보자.HTTP Patch 메소드데이터의 부분 업데이트를 요청한다.PUT과 달리 멱등성을 보장하지 않는다.캐시가 가능하지만, 본문까지 캐시 키로 봐야 해서 일반적으로 잘 쓰이지 않는다.Patch의 경우, 프론트와 백 중 한 곳에서 "어디에서 변경이 발생했는가"를 확인하는 작업이 필요하다.프론트가 편하게 만들 경우, 백엔드에서 뭐가 변경되었는지 명확히 알지 못하는 문제가 존재한다.그렇다고 각 수정 내용을 하나씩 Post 메소드로 ..

[PinIt] Dependency 도메인과 Schedule 도메인 간의 연관관계 풀기

문제가 되는 비즈니스 로직일정 간의 사이클 검사Dependency는 from/to 두 개의 schedule을 알고 있다.CycleChecker는 그 중 indegree가 0인 임의의 schedule을 잡고, 의존관계를 탐색하며 사이클이 존재하는지를 파악한다.일정 삭제일정 삭제 시, 해당 일정을 알고 있는 모든 의존관계를 제거한다.즉, Dependency 추가와 삭제 모두 관련된 모든 일정이 관리한다.이전 일정이 완료되었는지 확인일정은 이전 일정을 직접 알고있지 않다.Dependency라는 엔티티를 거쳐서 알고 있다.from.isCompleted()로 사전 완성 일정 체크 일정 간의 사이클 검사일정 간의 사이클 검사에는 Schedule 정보가 필요하지 않다.따라서, ownerId로 해당 사용자의 모든 연관..