no image
Test Code ? 왜 그리고 어떻게 작성해야할까 ?
테스트 코드를 왜 작성하는 것인가 ? 첫번째, 디버깅 비용 절감 개발자가 총 개발 소요 시간중 온전히 요구사항에 대한 기능 개발을 하는 시간이 전체의 비중에 어느정도를 차지할까? 애플리케이션은 항상 내결함성을 가지고 있고 좋은 코드와 설계를 갖추더라도 결함의 존재자체를 부정할 수 없다 그래서 기능에 대한 버그는 항시 발생하고 그것을 해결하기 위해 개발자는 디버깅을 꾸준히 할 수 밖에 없다 프로젝트를 진행하다보면 문제를 해결하는 시간보다 문제를 찾기 위한 시간이 더 많이 소비되는 것을 알 수 있을 것이다. 어느 코드 블록에서 어떤 케이스에서 문제가 발생하는지 알기 힘들기때문이다 그렇다면 테스트 코드가 있는 프로젝트라면 어떨까 ? 견고한 테스트를 작성하더라도 완벽하게 결함을 없앨 순 없다 하지만 테스트 코드..
2023.07.04
TIL
no image
MSA
MSA(MicroService Architecture) 마이크로서비스란 작고, 독립적으로 배포 가능한 각각의 기능을 수행하는 서비스로 구성된 프레임워크라고 할 수 있다. 마이크로서비스는 완전히 독립적으로 배포가 가능하고, 다른 기술 스택(개발 언어, 데이터베이스등)이 사용 가능한 단일 사업 영역에 초점을 둔다 등장배경 Monolithic Architecture 소프트웨어의 모든 구성요소가 한 프로젝트에 통합되어 있는 형태이다. 웹 개발을 예로 들면 웹 프로그램을 개발하기 위해 모듈별로 개발하고, 개발이 완료된 웹 어플리케이션을 하나의 결과물로 패키징하여 배포된 형태를 말한다. 웹의 경우 WAR파일로 빌드되어 WAS에 배포하는 형태를 말하며, 주로 소규모 프로젝트에 사용된다. 하지만 일정 규모 이상의 서비..
2023.07.03
TIL
no image
Redis ?
무조건 RDBMS를 사용해야하는 걸까? RDBMS는 실제 물리적 장치에 저장하기때문에 데이터를 영속적으로 관리할 수 있지만, 입출력에 다소 시간이 걸리는 단점이 존재한다. 이러한 여러가지 부분에 있어서 인증 토큰에 대해서는 Redis를 사용한다. Redis Remote Dictionary Server 약자로 Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템 (NoSQL) 특징 Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템 (NoSql) 데이터베이스, 캐시, 메세지 브로커로 사용된다 In-Memory 기반의 데이터 처리 및 저장을 제공하여 속도가 빠르지만 서버가 꺼지면..
2023.05.26
TIL
no image
FCM(Firebase Cloud Messaging)
FCM FCM은 Firebase Cloud Messaging의 약자로, 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션이다. 모든 사용자에게 알림 메세지를 전송할 수도 있고, 그룹을 지어 메시지를 전송할 수도 있다. Firebase의 서비스는 요금 정책에 따라, 이용할 수 있는 범위가 다르지만 FCM은 요금 정책에 구분 없이 무료로 사용하는 것이 가능하다. 왜 사용하는가 ? FCM을 이용하면 플랫폼에 종송되지 않고 푸쉬 메시지를 전송할 수 있다 iOS, Android, Web 각 플랫폼에서 푸쉬 메시지를 보내려면 각 플랫폼 환경 iOS (APNS), Android (GCM)별로 개발해야 하는 불편함이 있는데 이를 하나로 통합하는 솔루션으로 플랫폼에 종속되지 않고 Push 메시지를 전..
2023.05.26
TIL
no image
[GreenCherry/React, Spring boot, PWA, FCM] FCM을 활용한 백그라운드에서 알림 구현
그린체리 프로젝트를 바탕으로 알림서비스에 대한 구조를 간단하게 설명하자면, 프론트 단에서 처음 로그인을 할 경우, 1. 알림 허용에 대한 권한에 대해 묻는다. 2. 허용을 눌렀을 경우 각 아이디당 플랫폼에 해당한 유니크한 토큰을 발급하여 회원정보와 함께 DB에 저장한다. 프론트 단에서 알림관련 API 호출할 경우, 1. 백엔드 단에서 알림에 관련된 API가 호출되면서 해당 내용에 대한 알림을 회원에게 보낸다. 각 프론트, 백엔드에서 FCM이 무슨 역할을 하는지 이해하는 게 중요하다 프론트에서 FCM은 알림 허용 권한, 토큰 발급의 역할을 하는 대신, 백엔드에서 FCM은 알림을 프론트로 보내는 역할을 한다 실질적인 FCM은 백엔드에서 사용된다고 볼 수 있다 해당 프로젝트에선 백엔드 서버인 Java에서 알림..
2023.05.25
TIL
no image
[JPA] OSIV와 성능 최적화
Open Session In View : 하이버네이트 Open EntityManager In View : JPA (관례상 OSIV라 한다) OSIV ON spring.jpa.open-in-view : true 기본값 이 기본값을 뿌리면서 애플리케이션 시작 시점에 warn로그를 남기는 것은 이유가 있다. OSIV 전략은 트랜잭션 시작처럼 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지한다. 그래서 지금까지 View Template이나 API 컨트롤러에서 지연 로딩이 가능했던 것이다. 지연 로딩은 영속성 컨텍스트가 살아있어야 가능하고, 영속성 컨텍스트는 기본적으로 데이터베이스 커넥션을 유지한다. 이것 자체가 큰 장점이다. 그런데 이 전략은 너무 ..
2023.03.22
TIL
[JPA] API 개발 고급 정리
정리 엔티티 조회 엔티티 조회해서 그대로 반환 : V1 엔티티 조회 후 DTO로 변환 : V2 페치 조인으로 쿼리 수 최적화 : V3 컬렉션 페이징과 한계 돌파 : V3.1 컬렉션은 페치 조인시 페이징이 불가능 ToOne관계는 페치 조인으로 쿼리 수 최적화 컬렉션은 페치 조인 대신에 지연 로딩을 유지하고, hibernate.default_batch_fetch_size, @BatchSize로 최적화 DTO 직접 조회 JPA에서 DTO를 직접 조회 : V4 컬렉션 조회 최적화 - 일대다 관계인 컬렉션은 IN 절을 활용해서 메모리에 미리 조회해서 최적화 : V5 플랫 데이터 최적화 - JOIN 결과를 그대로 조회 후 애플리케이션에서 원하는 모양으로 직접 변화 : V6 권장 순서 엔티티 조회 방식으로 우선 접근..
2023.03.21
TIL
[JPA] 페이징과 한계돌파
컬렉션을 페치 조인하면 페이징이 불가능하다. 컬렉션을 페치조인하면 일대다 조인이 발생하므로 데이터가 예측할 수 없이 증가한다. 일대다에서 일(1)을 기준으로 페이징을 하는 것이 목적이다. 그렇데 데이터는 다(N)를 기준으로 row가 생성된다. Order를 기준으로 페이징하고 싶은데 다(N)인 OrderItem을 조인하면 OrderItem이 기준이 되어버린다. 더 자세한 내용은 자바 ORM 표준 JPA 프로그래밍 - 페치 조인 한계 참고 이 경우 하이버네이트는 경고 로그를 남기고 모든 DB 데이터를 읽어서 메모리에서 페이징을 시도한다. 최악의 경우 장애로 이어질 수 있다. 한계 돌파 그러면 페이징과 컬렉션 엔티티를 함께 조회하려면 어떻게 해야할까 ? 지금부터 코드도 단순하고, 성능 최적화도 보장하는 매우 ..
2023.03.20
TIL

테스트 코드를 왜 작성하는 것인가 ?

 

첫번째, 디버깅 비용 절감

개발자가 총 개발 소요 시간중 온전히 요구사항에 대한 기능 개발을 하는 시간이 전체의 비중에 어느정도를 차지할까?

애플리케이션은 항상 내결함성을 가지고 있고 좋은 코드와 설계를 갖추더라도 결함의 존재자체를 부정할 수 없다 그래서 기능에 대한 버그는 항시 발생하고 그것을 해결하기 위해 개발자는 디버깅을 꾸준히 할 수 밖에 없다

 

 

프로젝트를 진행하다보면 문제를 해결하는 시간보다 문제를 찾기 위한 시간이 더 많이 소비되는 것을 알 수 있을 것이다. 어느 코드 블록에서 어떤 케이스에서 문제가 발생하는지 알기 힘들기때문이다

 

 

그렇다면 테스트 코드가 있는 프로젝트라면 어떨까 ?

 

견고한 테스트를 작성하더라도 완벽하게 결함을 없앨 순 없다

하지만 테스트 코드를 통해 오류를 줄일 수 있고, 빠르게 대처할 수 있다

 

테스트의 분류

아래의 그림과 같이 범위와 비중에 따라 세분류로 나누어지고 각각의 역할을 가지고 있다

테스트 피라미드

  • Unit Test (단위 테스트)
    • 도메인 모델과 비즈니스 로직을 테스트, 작은 단위의 코드 및 알고리즘 테스트
    • 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위 테스트로, 여기서의 모듈은 애플리케이션에서 작동하는 하나의 메서드 혹은 기능을 의미한다
    • 해당 단위의 기능을 검증하고 예상되는 결과를 확인하는 목적으로 사용된다
  • Integration Test(통합 테스트)
    • 코드의 주요 흐름들을 통합적으로 테스트하며 주요 외부 의존성(ex. 데이터베이스)에 대해서 테스트
    • 여러 개의 모듈이나 시스템 구성 요소가 함께 동작할 때, 이들 간의 상호작용을 테스트하여 전체적인 시스템의 동작이 예상대로 이루어지는지 검증하는 테스트 방법으로 DB에 접근하거나 전체 코드와 다양한 환경이 제대로 작동하는지 확인하는데 필요한 모든 작업을 수행할 수 있다
  • e2e Test (인수 테스트, Acceptance Test)
    • 최종 사용자의 흐름에 대한 테스트이며 외부로부터의 요청부터 응답까지 기능이 잘 동작하는지에 대한 테스트
    • 즉, 사용자 시나리오를 기반으로 작성하여 소프트웨어가 제공해야 할 기능이나 사용자가 어떠한 목적으로 소프트웨어를 사용하는지에 대한 내용을 담고 있으며, 일반적으로 기능 테스트 이후 수행된다

이렇게 구분한 테스트 구간의 경계로 인해 문제가 되는 코드의 범위와 케이스를 빠르게 파악하고 수정할 수 있게 되었다.

테스트로 인해 결함을 최소화시켜 디버깅에 소모되는 시간을 줄어듬으로써, 서비스 개발자가 비즈니스 개발에 집중하면서 생산성 향상에 도움을 주게 된다.

 

 

두번째, 코드 변경에 대한 불안감 해소

애플리케이션에서 기능은 단일 하나의 요소(함수, 객체, 도메인)로 이루어지지 않고 여러 요소들이 서로 상호작용하고 협력하여 만들어지기때문에 하나의 기능을 수정할 때 구성된 요소가 다른 기능과 협력하고 있고 영향을 준다

그렇기때문에 회귀 버그를 겪게 되는 것이다

 

그렇다면 회귀 버그를 완전히 차단하고 예방할 수 있을까

 

회귀 버그는 예방하는 것이 아니라 관리하고 대처해야 한다

다시 말해 회귀 테스트를 해야 한다

 

회귀 테스트는 기능 추가나 오류 수정으로 인해 새롭게 유입되는 오류가 없는지 검증한다

테스트 코드는 그때 당시의 기능을 만들기 위해서만 필요한 코드가 아니다

그 이후에

  • 요구사항이 변경되어 기존 코드를 수정하거나
  • 더 나은 코드를 위해 리팩토링을 하거나

서비스가 지속 가능하게 발전하기 위해 필요한 코드이다

 

 

세번째, 더 나은 문서 자료

좋은 코드를 작성하기 위해 잘 읽히는 코드, 가독성 좋은 코드를 작성하기 위해 노력한다

하지만 서비스 복잡도가 늘어남에 따라 코드의 양이 많아지고 코드의 복잡성가 높아지게 된다

이러한 복잡한 요구사항이 중첩되어 있는 기능들은 신규 입사자나 투입자들이 이 코드를 이해하는데 많은 불편함을 겪게한다

 

 

보통은 편의를 위해 따로 문서화를 해두지만 쉽게 방치되는 문서화의 문제점이 있다

 

 

이 문제점이 발생하는 이유가 무엇일까?

두 대상을 분리하고 물리적인 거리감이라는 이유로 코드의 수정에 맞춰 문서의 최신화가 잘 이루어지지 않는 경우가 많기때문에 소스 코드외의 별도의 문서자료들은 관리 대상으로서 쉽게 제외되기때문에 신뢰하기 어려워진다

 

 

테스트를 작성할 때 가장 먼저 명세를 작성하면서 코드의 실제 동작을 기술함으로써 이 코드가 어떤 역할을 가졌는지 이해하는데 도와준다

기존에 중복된 메일이 있는 경우 회원가입을 할 수 없습니다.

문서화 테스트는 기능과 코드를 이해하는 데 도움을 준다

 

 

네번째, 좋은 코드는 테스트하기 쉽다

좋은 코드는 “변경하기 쉬운”이라는 형용사를 가지고 있다

이 의미는 약한 결합도를 가지고 있는 코드를 뜻하며 반대로 강결합이 되어있는 코드는 유지비용이 증감되어 저품질코드로 분류되어진다

 

외부의 영향을 받거나 내부적으로 의존성을 가지고 있는 코드는 변경에 유연하게 대응하지 못하고 재사용하기 어려운 코드들이다. 그렇기때문에 테스트를 작성하기 어려운 코드가 만들어지는 것이다

 

그렇다고 테스트하기 쉬운 코드가 모두 좋은 코드가 되는 것은 아니다. 하지만 테스트를 작성하면서 하나의 좋은 코드의 지표를 아래와 같이 세울 수 있게 된다

“만약 내가 작성한 코드가 테스트하기 어려운 코드라면 냄새나는 코드일 가능성이 높아”

 

다섯번째, 테스트 자동화

개발자의 기도메타는 본인이 작성한 코드가 실제 운영 환경에 배포됐을 때 불안감에 휩싸여 기도를 시작한다

 

하지만 자동화 테스트가 있는 순간부터 우리는 CI에서 우리가 작성한 테스트 코드로 버그가 배포되는 것을 방지할 수 있다.

변경에 대한 내성과 회귀 버그를 지속적인 코드 병합 단계에서 확인할 수 있으므로 소프트웨어의 품질 또한 높여준다

 

즉, 테스트 코드가 존재함으로써 안전감 있는 프로젝트, 개발자의 모습을 볼 수 있게 된다

 

 

코드를 잘 작성하는 방법 with Jest

지금까지는 “테스트 코드를 왜 작성하는 것인가?”에 대해서 5가지 이유를 알아보았다

테스트 코드를 작성해야 되는 이유를 알았다면 이제 테스트 코드를 어떻게 작성해야 하는지에 대해서 알아보겠다

 

 

첫번째, 테스트 코드는 DRY 보다는 DAMP 하게 작성하라

개발 원칙 중 DRY(Don’t Repeat Yourself) 원칙을 많이 들어봤을 것이다

개발자는 중복 코드를 싫어해 중복 코드가 보이면 그것을 어떻게든 없애려고 하는 경향이 있다

그렇다면 중복을 줄이기 전에 DAMP하게 테스트 코드를 작성하는 것을 고려해봐야 한다

 

 

DAMP 원칙Descriptive and Meaningful Phrases의 약자로 의미있고 설명적인 구문을 사용하라는 원칙이다

 

 

테스트 코드의 중복을 줄이면서 테스트 간의 결합도를 높일 수 있다 또한, 이전보다 가독성이 떨어져 테스트 코드를 작성하는 사람도 테스트 코드를 파악하는 사람도 어려운 테스트가 될 수 있다

 

 

이러한 문제를 해결하기 위해 테스트 코드의 리팩토링은 중복을 줄이는 것이 아니라 더 서술적이고 의미있게 작성하는 방향으로 이루어져야 한다

 

 

오히려 무작정 코드의 중복을 줄이면 테스트 간의 격리성이 떨어지고 테스트 본래의 의도가 점점 모호해져 파악을 제대로 할 수 없게 되어지기때문이다

 

 

Better :

  • 테스트의 중복을 줄이는 것이 아니라 더 서술적이고 의미있게 작성하는 방향으로 리팩터링을 해야 한다
  • 테스트는 서로 독립적이고 격리되어야 하기때문에 테스트 수정에 다른 테스트가 영향을 받지 않도록 해야 한다
  • DAMP 원칙을 지키면서 중복을 줄이는 방안으로는 테스트 픽스쳐 함수나 클래스등을 사용할 수 있다

 

 

두번째, 테스트는 구현이 아닌 결과를 검증하도록 한다

테스트 코드를 작성할 때 빠른 테스트를 위해 모의 객체를 사용할 때가 있다

모의 객체로 mock, spy, stub등을 사용하는 경우 테스트 대상의 구현을 알아야만 테스트를 작성할 수 있다

그런데, 이 부분을 남용하거나 잘못 사용하여 테스트를 작성하는 경우가 종종 있다

 

 

내부 구현이나 비공개(private) 메소드들은 언제든지 바뀔 여지가 있는 코드이기때문에 정보의 은닉을 위해 숨기는데 다시 정보를 꺼내여 테스트하는 것은 좋지 않다

 

 

테스트 코드는 내부 구현보다는 실행 결과에 집중하는 것이 리팩토링 내성을 높일 수 있다

 

 

지나친 모의 객체 남용과 내부 구현 검증은 오히려 리팩터링의 내성을 떨어뜨리고 유지보수를 더 어렵게 만든다

 

 

우리는 어떻게 라는 내부 구현 검증보다는 무엇을 이라는 결과 검증에 집중하여 테스트 코드를 작성해야 한다

 

Better :

  • 테스트 코드는 내부 구현이 아닌 실행 결과에 집중해야 한다
  • 의미 있는 테스트, 검증을 하자

 

 

세번째, 읽기 좋은 테스트를 작성하라

테스트 코드는 메인, 제품 코드을 위한 코드라 할지라도 우리의 책임이고 관리 대상이다

테스트 코드도 가독성이 좋아야하고 불명확한 테스트 코드는 읽는 행위도 유지보수 하는 행위도 어렵게 만든다

 

 

좋은 테스트 코드는 읽는 사람 입장에서 이 테스트를 이해하는데 필요한 모든 정보를, 테스트 케이스 본문에 담고 있는 테스트를 말한다

 

 

그리고 이와 동시에 관련없는 정보를 담지 말아야 한다

 

또한 테스트 코드의 가독성을 높이는 방법 중 하나는 테스트의 구조를 잘 잡는 것이다

 

 

테스트의 구조는 준비, 실행, 검증 3개의 구절로 나뉘어질 수 있다

각각의 구절을 AAA패턴(Arrange-Act-Assert 주석으로 구분)이나 GWT 패턴(given-when-then 행위 주석으로 구분)으로 구간을 나누어 작성하는 것이 좋다

*물론 구조를 위한 주석을 사용하지 않고 각 영역에 맞게 테스트 함수 내 코드를 구분해서 작성할 수 있다면 그 방안을 사용하는 것이 좋다

 

 

그리고 만약 테스트 안에 너무 많은 양의 코드가 존재한다면 이 부분을 좀 더 코드 재사용의 목적으로 모듈화(테스트 팩토리, 빌더, 헬퍼 메소드)해두면 쉽게 읽힐 수 있게 된다

 

 

Better :

  • 테스트 코드도 코드이므로 읽기 쉬운 코드를 작성하자
  • 무엇이 잘못되어 실패했는지, 어떻게 고쳐야 하는지를 파악하기 어려운 테스트를 작성하지 말자
  • 테스트 구조를 나누는 패턴과 테스트 코드를 모듈화하는 방법을 통해 테스트 코드를 읽기 쉽게 작성하자

 

 

네번째, 테스트 명세에 비즈니스 행위를 담도록 한다

마지막으로 테스트 명을 작성할 때도 유의할 점이 있다

테스트명은 퍼블릭 인터페이스와 마찬가지로 명확하게 의도가 들어나도록 작성이 되어야 한다

테스트 코드 작성 이유 중 좋은 테스트 코드는 우리가 작성한 코드의 문서이기에 명확한 의도가 담긴 명세가 되어야 한다

 

 

여기서 명확하게 의도가 드러나는 이름은 개발자의 용어가 아닌 비즈니스 행위를 담은 비개발자가 읽을 수 있게 설명되어야 한다

 

 

Bad:

if(’관리자를 생성한 후 관리자 정보를 확인한다.’)

 

Better:

if(’관리자 정보로 가입한다’)

이렇게 잘 작성된 이름은 다음과 같은 효과를 가지고 온다

  • 테스트 코드와 실행 로그를 확인하지 않아도, 동작을 유추할 수 있으며, 요구사항이 빠르게 공유가 될 수 있다
  • 영향 범위를 빠르게 파악할 수 있으며 유추를 통하여 빠른 복구를 할 수 있다

 

 

테스트 코드 작성이 어려운 이유

하지만 테스트 코드를 작성하는 것은 쉽지 않은 작업이며 시간과 비용이 많이 든다

  • 개발자들은 주로 코드 작성에 집중한다
  • 단위 테스트의 경우 테스트 케이스 수가 많아질 수 있으며, 작성하고 관리하는데 쉽지 않다
  • 코드 수정 및 리팩토링이 발생할때마다 테스트 코드도 같이 변경해주어야 한다
  • 테스트 코드를 작성하면서 발생하는 문제점도 해결해야 한다

 

 

마무리

테스트 코드는 시간과 비용이 들더라도 “지속 가능한 서비스, 프로젝트”를 위해 필수적인 요소 중 하나이다

 

 

간단한 프로젝트에서는 테스트 코드의 영향을 확인하긴 힘들겠지만 서비스를 제공하는 회사

즉, 실무에서는 서비스, 기능등을 올바르게 수행하여 서비스를 이용하는 사용자들에게 제공해주어야 하기때문에 더욱 더 실무에선 테스트 코드의 중요성을 알고 투자를 하고 있을 것이다.

뿐만아니라 문서의 역할도 하기에 혼자가 아닌 팀으로 개발 할 경우 테스트 코드는 코드의 생산성과 개발시간의 단축을 시킬 수 있는 중요한 작업 중 하나이다

 

 

 

 

 

 

 

테스트 코드를 왜 그리고 어떻게 작성해야 할까?

테스트 코드가 필요한 이유와 잘 작성하는 방법에 대해 공유합니다.

tech.inflab.com

 

'TIL' 카테고리의 다른 글

MSA  (0) 2023.07.03
Redis ?  (0) 2023.05.26
FCM(Firebase Cloud Messaging)  (0) 2023.05.26
[GreenCherry/React, Spring boot, PWA, FCM] FCM을 활용한 백그라운드에서 알림 구현  (0) 2023.05.25
[JPA] OSIV와 성능 최적화  (0) 2023.03.22

MSA

개발의 숩
|2023. 7. 3. 11:14

MSA(MicroService Architecture)

마이크로서비스란 작고, 독립적으로 배포 가능한 각각의 기능을 수행하는 서비스로 구성된 프레임워크라고 할 수 있다. 마이크로서비스는 완전히 독립적으로 배포가 가능하고, 다른 기술 스택(개발 언어, 데이터베이스등)이 사용 가능한 단일 사업 영역에 초점을 둔다

 

 

 

등장배경

Monolithic Architecture

소프트웨어의 모든 구성요소가 한 프로젝트에 통합되어 있는 형태이다.

웹 개발을 예로 들면 웹 프로그램을 개발하기 위해 모듈별로 개발하고, 개발이 완료된 웹 어플리케이션을 하나의 결과물로 패키징하여 배포된 형태를 말한다.

웹의 경우 WAR파일로 빌드되어 WAS에 배포하는 형태를 말하며, 주로 소규모 프로젝트에 사용된다.

하지만 일정 규모 이상의 서비스, 혹은 수백명 이상의 개발자가 투입되는 프로젝트에서 Monolithic Architecture는 한계를 보인다

 

 

 

Monolithic Architecture 한계

 

부분 장애가 전체 서비스의 장애로 확대될 수 있다.

개발자의 잘못된 코드 배포 또는 갑작스러운 트래픽 증가로 인해 성능에 문제가 생겼을 때, 서비스 전체의 장애로 확대될 수 있다.

 

부분적인 Scale-out이 어렵다

Monolithic Architecture에서는 사용되지 않는 다른 모든 서비스가 Scale-out되어야 하기때문에 부분 Scale-out이 어렵다

Scale-out : 여러 server로 나누어 일을 처리하는 방식

 

서비스의 변경이 어렵고, 수정시 장애의 영향도 파악이 힘들다

여러 컴포넌트가 하나의 서비스에 강하게 결합되어 있기때문에 수정에 대한 영향도 파악이 힘들다

 

배포 시간이 오래 걸린다

최근 어플리케이션 개발 방법은 CI/CD를 통한 개발부터 배포까지 빠르게 반영하는 추세이다.

그러나 규모가 커짐에 따라 작은 변경에도 높은 수준의 테스트 비용이 발생하기도 하며, 많은 사람이 하나의 시스템을 개발하여 배포하기때문에 영향을 줄 수 밖에 없다

 

한 Framework와 언어에 종속적이다

Spring Framework를 사용할 경우, 블록체인 연동 모듈을 추가할 때 node.js를 사용하는 것이 일반적이며 강세이다. 그러나 선정했던 Framework가 Spring이기때문에 Java를 이용하여 해당 모듈을 작성할 수 박에 없다.이러한 Monolithic Architecture의 한계를 보완하기 위해 MSA가 등장하게 되었다. 기존의 특정한 물리적인 서버에 서비스를 올리던 on-promise 서버 기반 Monolithic Architecture에서 이제는 클라우드 환경을 이용하여 서버를 구성하는 MicroService Architecture로 많은 서비스들이 전환되고 있다.

 

 

특징

MSA는 API를 통해서만 상호작용할 수 있다.

즉, 마이크로서비스는 서비스의 end-point(접근점)을 API 형태로 외부에 노출하고, 실질적인 세부사항은 모두 추상화한다. 내부의 구현 로직, 아키텍처와 프로그래밍 언어, 데이터베이스, 품질 유지 체계와 같은 기술적인 사항들은 서비스 API에 의해 철저하게 가려진다

따라서 SOA(Service Oriented Architecture)의 특징을 다수 공통으로 가진다

 

 

제대로 설계된 마이크로서비스는 하나의 비즈니스 범위에 맞춰 만들어지므로 하나의 기능만 수행한다

즉, 어플리케이션 출시처럼 하나의 목표를 향해 일하지만, 자기가 개발하는 서비스만 책임진다 또한, 여러 어플리케이션에서 재사용할 수 있어야 한다

 

어플리케이션은 항상 기술 중립적 프로토콜을 사용해 통신하므로 서비스 구현 기술과는 무관하다

따라서, 마이크로서비스 기반의 어플리케이션을 다양한 언어와 기술로 구축할 수 있다는 것을 의미한다

 

마이크로서비스는 SOA에서 사용되는 집중화된 관리체계를 사용하지 않는다.

마이크로서비스 구현체의 공통적인 특징 중 하나는 ESB(Enterprise Service Bus)와 같은 무거운 제품에 의존하지 않는다는 점이다. REST 등 가벼운 통신 아키텍처, 또는 Kafka등을 이용한 message stream을 주로 사용한다

 

 

장점

각각 개별의 서비스 개발을 빠르게 하며, 유지보수도 쉽게할 수 있도록 한다

각각의 서비스가 모듈화되어 있으며, 이러한 모듈끼리는 RPC 또는 message-driven API등을 이용하여 통신한다

 

팀 단위로 적절한 수준에서 기술 스택을 다르게 가져갈 수 있다

 

서비스별로 독립적 배포가 가능하다

따라서 지속적인 배포 CD도 모놀리식에 비해서 가볍게 할 수 있다

 

각각 서비스의 부하에 따라 개별적으로 scale-out이 가능하다

메모리, CPU적으로 상당부분 이득이 된다

 

 

단점

모놀리식에 비해 상대적으로 많이 복잡하다

서비스가 모두 분산되어 있기대문에 개발자는 내부 시스템의 통신을 어떻게 가져가야 할지 정해야 한다

또한, 통신의 장애와 서버의 부하등이 있을 경우 어떻게 트랜잭션을 유지할지 결정하고 구현해야 한다

 

트랜잭션 유지가 어렵다

모놀리식에서는 단일 트랜잭션을 유지하면 됐지만 MSA에서는 비즈니스에 대한 DB를 가지고 있는 서비스도 각기 다르고, 서비스의 연결을 위해서는 통신이 포함되기때문에 트랜잭션을 유지하는게 어렵다

 

통합 테스트가 어렵다

개발 환경과 실제 운영 환경을 동일하게 가져가는 것이 쉽지 않다

 

실제 운영 환경에 대해서 배포하는 것이 쉽지 않다

마이크로서비스의 경우 서비스 1개를 재배포한다고 하면 다른 서비스들과의 연계가 정상적으로 이루어지고 있는지도 확인해야 한다

 

 

기업으로 알아본 적용 사례

 

이모티콘 서비스는 왜 MSA를 선택했나?

성장을 위해 달려오느라 거대해진 이모티콘 서비스와 그만큼 많이 쌓인 기술 부채를 두고, 천년만년 행복하게 개발하려는 구성원들이 선택한 MSA. 기존 레거시 서비스가 단일 서버로 너무 큰 코

tech.kakao.com

 

넷플릭스로 알아보는 MSA | 인사이트리포트 | 삼성SDS

 

www.samsungsds.com

 

 

서비스 경량화를 위한 MSA 설계 시 고려사항 | 인사이트리포트 | 삼성SDS

 

www.samsungsds.com

 

 

11번가 주니어 개발자의 첫 MSA 설계 및 개발기 | 11번가 TechBlog — 11번가 기술블로그

안녕하세요. 11번가 주문개발팀 개발자 고다경입니다. 입사 후 파일럿 프로젝트를 진행한 것을 Product로 전환하는 과정을 담았습니다. 많이 부족한 글이지만 신입~주니어의 글이라 생각해주시고

11st-tech.github.io

 

 

 

 

SOA(Service Oriented Architecture)

대규모 컴퓨터 시스템을 구축할 때의 개념

업무상 일 처리에 해당하는 소프트웨어 기능을 서비스로 판단하고 그 서비스를 네트워크상에 연동하여 시스템 전체를 구축해가는 방법론이다. 업무 처리 변화를 시스템에 빠르게 반영하고자 하는 수요에 대응하기 위해 2004년부터 IT업계에서 주목하고 있다

  • 플랫폼에 종속되지 않는 표준 인터페이스를 통해 기업의 업무를 표현한 ‘느슨하게 연결되고(Loosly coupled) 상호 조합 가능한 소프트웨어
  • SOA에서는 각각의 서비스가 데이터 계층, 비즈니스 로직, 뷰에 대한 모듈을 모두 가지고 있고, 각 서비스 간의 의존성이 최소화된다
  • SOA시스템의 규모가 증가함에 따라 서비스의 중복이 발생할 수 있고, 이를 방지하기 위해 이미 구현된 서비스가 있는지 검색할 수 있어야 한다

SOA 아키텍처 모델

  1. Fundamental SOA(통합)
  2. Networked SOA(유연성과 통제 추가)
  3. Process Oriented SOA(민첩성의 추가)

Redis ?

개발의 숩
|2023. 5. 26. 10:08

무조건 RDBMS를 사용해야하는 걸까?

RDBMS는 실제 물리적 장치에 저장하기때문에 데이터를 영속적으로 관리할 수 있지만,

입출력에 다소 시간이 걸리는 단점이 존재한다. 이러한 여러가지 부분에 있어서 인증 토큰에 대해서는 Redis를 사용한다.

 

Redis

Remote Dictionary Server 약자로 

Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템 (NoSQL)

 

특징

  • Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터 베이스 관리 시스템 (NoSql)
  • 데이터베이스, 캐시, 메세지 브로커로 사용된다
  • In-Memory 기반의 데이터 처리 및 저장을 제공하여 속도가 빠르지만 서버가 꺼지면 모든 데이터가 사라지기 때문에 보조 데이터베이스로 사용된다.
  • 데이터를 디스크에 쓰는 구조가 아니라 메모리에서 데이터를 처리하기때문에 속도가 빠르다
  • String, Lists,Sets,Sorted Sets, Hashes 자료 구조를 지원한다
    • String : 가장 일반적인 key-value 구조의 형태
    • Sets : String의 집합, 여러 개의 값을 하나의 value에 넣을 수 있다
    • Sorted Sets : 중복된 데이터를 담지 않는 Set 구조에 정렬 Sort를 적용한 구조로 사용할 수 있다
    • Lists : Array 형식의 데이터 구조, List를 사용하면 처음과 끝에 데이터를 넣고 빼는 건 빠르지만 중간에 데이터를 삽입하거나 삭제하는 것은 어렵다
  • Single Threaded
    • 한 번에 하나의 명렁만 처리할 수 있다 그렇기 때문에 중간에 처리 시간이 긴 명령어가 들어오면 그 뒤에 명령어들은 모두 앞에 있는 명령어가 처리될 때까지 대기가 필요하다 (하지만 get, set 명령어의 경우 초당 10만 개 이상 처리할 수 있을 만큼 빠르다)

 

 

SOCK 프로젝트에선 Redis의 특징 중 Single Threaded를 활용하여 레시피 인기도 상승에 대한 로직을 구현하였다

 

구현 방식에 대한 자세한 내용은 다음 포스팅에 작성하도록 하겠다

 

 

 

데이터 베이스가 있는데도 Redis라는 인메모리 데이터 구조 저장소를 사용하는 이유가 무엇일까

데이터베이스는 데이터를 물리 디스크에 직접 쓰기때문에 서버에 문제가 발생하여 다운되더라도 데이터가 손실되지 않는다. 하지만 매번 디스크에 접근해야 하기때문에 사용자가 많아질수록 부하가 많아져서 느려질 수 있다

일반적으로 서비스 운영 초반이거나 규모가 작은, 사용자가 많지 않은 서비스의 경우에는 WEB -WAS -DB 의 구조로도 데이터베이스에 무리가 가지 않는다.

하지만 사용자가 늘어난다면 데이터베이스가 과부하될 수 있기때문에 이때 캐시 서버를 도입하여 사용한다

그리고 이 캐시 서버로 이용할 수 있는 것이 바로 Redis이다

캐시는 한번 읽어온 데이터를 임의의 공간에 저장하여 다음에 읽을 때는 빠르게 결괏값을 받을 수 있도록 도와주는 공간이다

같은 요청이 여러번 들어오는 경우 매번 데이터 베이스를 거치는 것이 아니라 캐시 서버에서 첫 번째 요청 이후 저장된 결괏값을 바로 내려주기때문에 DB의 부하를 줄이고 서비스의 속도도 느려지지 않는 장점이다

캐시 서버는 Look aside cashe 패턴Write Back 패턴이 존재한다

 

Look aside cache

  1. 클라이언트가 데이터 요청
  2. 웹 서버는 데이터가 존재하는지 Cache 서버 먼저 확인
  3. Cache 서버에 데이터가 존재하면, DB에 데이터를 조회하지 않고 Cache 서버에 있는 결괏값을 클라이언트에게 반환 (Cache Hit)
  4. Cache 서버에 데이터가 존재하지 않다면, DB에 데이터를 조회하여 Cache 서버에 저장하고 결괏값을 클라이언트에게 반환 (Cache Miss)

 

Write Back

  1. 웹 서버는 모든 데이터를 Cache 서버에 저장
  2. Cache 서버에 특정 시간 동안 데이터가 저장됨
  3. Cache 서버에 있는 데이터를 DB에 저장
  4. DB에 저장된 Cache 서버의 데이터를 삭제
  • insert 쿼리를 한 번씩 500번 날리는 것보다 insert 쿼리 500개를 붙여서 한 번에 날리는 것이 더 효율적이다는 원리이다
  • 이 방식은 들어오는 데이터들이 저장되기 전에 메모리 공간에 머무르는데 이때 서버에 장애가 발생하여 다운된다면 데이터가 손실될 수 있다는 단점이 있다

 

Redis 사용에 주의할 점

  • 서버에 장애가 발생했을 경우 그에 대한 운영 플랜이 꼭 필요하다
  • : 인메모리 데이터 저장소의 특성상, 서버에 장애가 발생했을 경우 데이터 유실이 발생할 수 있다
  • 메모리 관리가 중요하다
  • 싱글 스레드의 특성상, 한 번에 하나의 명령만 처리할 수 있다. 처리하는데 시간이 오래 걸리는 요청, 명령은 피해야 한다

이 외에도 Master-Slave 형식의 데이터 이중화 구조에 대한 Redis Replication, 분산 처리를 위한 Redis cluster, 장애 복구 시스템 Redis Sentinel, Redis Topology, Redis Sharding, Redis Failover 등의 Redis를 더 효율적으로 사용하기 위한 개념들이 존재한다

 

 

 

 

📮개인 공부를 위한 공간으로 틀린 부분이 있을 수도 있습니다.📮

문제점 및 수정 사항이 있을 시, 언제든지 댓글로 피드백 주시기 바랍니다.

FCM(Firebase Cloud Messaging)

개발의 숩
|2023. 5. 26. 09:16

FCM

FCM은 Firebase Cloud Messaging의 약자로, 무료로 메시지를 안정적으로 전송할 수 있는 교차 플랫폼 메시징 솔루션이다.

모든 사용자에게 알림 메세지를 전송할 수도 있고, 그룹을 지어 메시지를 전송할 수도 있다.

Firebase의 서비스는 요금 정책에 따라, 이용할 수 있는 범위가 다르지만 FCM은 요금 정책에 구분 없이 무료로 사용하는 것이 가능하다.

 

왜 사용하는가 ?

FCM을 이용하면 플랫폼에 종송되지 않고 푸쉬 메시지를 전송할 수 있다

iOS, Android, Web 각 플랫폼에서 푸쉬 메시지를 보내려면

각 플랫폼 환경 iOS (APNS), Android (GCM)별로 개발해야 하는 불편함이 있는데

이를 하나로 통합하는 솔루션으로 플랫폼에 종속되지 않고 Push 메시지를 전송할 수 있다.

 

A→ 어플리케이션 서버 → 클라우드 메시징 서버 → B 와 같은 형태로 작업을 처리하게 된다.

 

 

 

클라우드 메시징 서비스를 사용하면 어떤 이점이 있는가?

서버를 경유해서 실시간으로 푸쉬 메시지를 받으려면 사용자는 항상 서버에 접속해있어야 하는데, 이는 사용자 기기의 배터리 및 네트워크 리소스를 크게 낭비한다.N

클라우드 메시징 서버를 중간에 둠으로써, 사용자는 낮은 배터리와 네트워크의 사용만으로도 메세지를 실시간으로 송수신 처리를 할 수 있다.

위와 같은 이유로 대부분의 어플리케이션 서비스들은 클라우드 메시징 서버를 경유해서, 실시간으로 유저들에게 메시지를 전송해주고 있다.

 

 

 

FCM의 주요 특징

메세지 타입

  • FCM의 메세지 타입은 알림 메세지와 데이터 메시지로 구분할 수 있다.
  • 보통 알림 메세지와 데이터 메시지를 같이 혼용해서 사용한다
    • 예를 들어 휴태폰 푸시 알림 메세지는 알림 메세지를 이용하고, 알림 메시지를 클릭하였을 때 앱내 특정 페이지로 이동이나, 어떠한 액션은 데이터 메세지를 통해서 이뤄진다.

메세지 종류 알림 가능 여부 알림 저장 개수 알림 처리 방법

알림 메시지 가능 여러 알림을 저장하나, OS 환경마다 다름 앱이 백그라운드일 경우
데이터 메시지 가능 1개의 알림만 저장 앱이 포그라운드일 경우

티켓팅

  • 단일 기기, 기기 그룹, 주제를 구독한 기기 3가지 방식으로 클라이언트 앱에 메시지를 배포할 수 있다.

종류 대상 수 설명

단일 기기 1개 하나의 기기(앱 기준)
기기 그룹 20개 알림 키에 허용되는 그룹
주제 구독 1000개 등록 토큰에 구독된 기기

클라이언트 앱에서 메시지 전송

  • FCM을 이용하면 앱 서버에서 클라이언트 앱으로 다운 스트림 메시지를 보낼 수 있을 뿐만 아니라, 클라이언트 앱에서 앱 서버로도 업 스트림 메세지를 보낼 수 있다.
  • 하지만 클라이언트 앱에서 앱 서버로 업스트림 메세지를 보내기 위해서는 선행 조건이 필요하다. 그 부분은 밑에서 설명한다.

 

 

FCM의 동작 원리

크게 송신자, FCM Backend Server, 수신자로 구분한다.

A→ 어플리케이션 서버 → 클라우드 메시징 서버 → B 와 같은 형태로 작업을 처리하게 된다.

송신자

주로 앱 서버, HTTP 프로토콜을 사용하는 서버, 우리가 흔히 사용하는 iOS, Android 운영체제를 사용하는 모바일 기기가 될 수 있다.

 

FCM Backend Server

실질적으로 앱 서버에서 요청을 받아서 메세지를 처리하는 서버에 해당된다.

메시지를 수신할 클라이언트는 자신의 정보를 FCM Backend Server에 등록해야 한다

메세지를 전송할 주체(앱 서버)는 등록된 정보를 획득해야 하며, 해당 정보로 다운 스트림 메시지를 전송한다.

앱 서버에서 FCM Backend Server에 메세지 요청을 보내고, FCM Backend Server는 사용자 기기에서 실행되는 클라이언트 앱에 메세지를 보내게 된다.

 

 

 

📮개인 공부를 위한 공간으로 틀린 부분이 있을 수도 있습니다.📮

문제점 및 수정 사항이 있을 시, 언제든지 댓글로 피드백 주시기 바랍니다.

 

'TIL' 카테고리의 다른 글

MSA  (0) 2023.07.03
Redis ?  (0) 2023.05.26
[GreenCherry/React, Spring boot, PWA, FCM] FCM을 활용한 백그라운드에서 알림 구현  (0) 2023.05.25
[JPA] OSIV와 성능 최적화  (0) 2023.03.22
[JPA] API 개발 고급 정리  (1) 2023.03.21

그린체리 프로젝트를 바탕으로 알림서비스에 대한 구조를 간단하게 설명하자면,

 

프론트 단에서 처음 로그인을 할 경우,

1. 알림 허용에 대한 권한에 대해 묻는다.

2. 허용을 눌렀을 경우 각 아이디당 플랫폼에 해당한 유니크한 토큰을 발급하여 회원정보와 함께 DB에 저장한다.

 

프론트 단에서 알림관련 API 호출할 경우,

1. 백엔드 단에서 알림에 관련된 API가 호출되면서 해당 내용에 대한 알림을 회원에게 보낸다.

 

 

각 프론트, 백엔드에서 FCM이 무슨 역할을 하는지 이해하는 게 중요하다

프론트에서 FCM은 알림 허용 권한, 토큰 발급의 역할을 하는 대신, 

백엔드에서 FCM은 알림을 프론트로 보내는 역할을 한다

실질적인 FCM은 백엔드에서 사용된다고 볼 수 있다

 

해당 프로젝트에선 백엔드 서버인 Java에서 알림을 구현했지만, Node.js,Python 등에서도 구현이 가능하다고 한다.

 

 

 

프로젝트에서 FCM을 사용한 이유는 간단하다

1. 플랫폼 종속성 없이 push 메시지 전송 가능

2. 사용자 기기의 배터리 및 네트워크 리소스 절약

3. 백그라운드를 통한 푸시 알림 기능

 

 

1. 플랫폼 종속성 없이 push 메시지 전송 가능

iOS,Android, Web 각 플랫폼에서 push 메시지를 보내려면 각 플랫폼별로 개발해야 하는 불편함이 있는데,

FCM을 이용하면 플랫폼에 종속되지 않고 push 메시지를 전송할 수 있다

 

 

2. 사용자 기기의 배터리 및 네트워크 리소스 절약

서버를 경유해서 실시간 push 메시지를 받으려면 사용자는 항상 서버에 접속해야하는데,

이는 사용자 기기의 배터리 및 네트워크 리소스를 크게 낭비한다

 

클라우드 메시징 서비스를 사용하면

사용자는 낮은 배터리와 네트워크의 사용만으로도 메시지를 실시간으로 송수신 처리를 할 수 있다

이와 같은 이유로 대부분의 어플리케이션 서비스들은 클라우드 메시징 서버를 경유해서,

실시간으로 유저들에게 메시지를 전송해주고 있다고 한다

 

3. 백그라운드를 통한 푸시 알림 기능

PWA의 Service Worker를 활용하여 웹 페이지와는 별개로 백그라운드 동기화나 푸시 알림 기능을 구현하기 위해 선택했다

 

 

구현 단계 정리

[Frontend] React에서 PWA 구현 단계

  1. PWA Manifest 설정하기 PWA Manifest를 설정하기 위해서는 public/manifest.json 파일을 생성합니다. 이 파일에서는 앱의 이름, 아이콘, 시작 URL 등을 정의할 수 있다
  2. Service Worker 구현하기 Service Worker는 public/service-worker.js 파일에서 구현할 수 있습니다. 이 파일에서는 Service Worker의 라이프사이클 이벤트를 처리하고, 오프라인 캐싱 등의 기능을 구현할 수 있다
  3. Push Notification을 위한 FCM 설정하기 Firebase Cloud Messaging(FCM)을 이용하여 Push Notification을 구현할 수 있다 이를 위해서는 Firebase 프로젝트를 생성하고, FCM을 설정해야 합니다. React에서는 firebase 모듈을 이용하여 FCM SDK를 사용할 수 있다
  4. 알림 목록을 가져올 API 구현하기 알림 목록을 가져오기 위한 API를 구현해야 한다. 이를 위해서는 Spring Boot Java 백엔드에서 알림을 저장하고, 필요한 API를 구현한다.
  5. React 프론트엔드에서 API 호출하기 React에서는 axios 또는 fetch 등의 라이브러리를 이용하여 API를 호출할 수 있다. 필요한 API를 호출하여 알림 목록을 가져와야 한다.
  6. 알림을 표시하는 컴포넌트 구현하기 알림 목록을 가져온 후에는 알림을 표시하는 컴포넌트를 구현해야 한다. 이를 위해서는 React에서 Notification API를 이용할 수 있다.
  7. PWA를 등록하기 PWA를 등록하기 위해서는 index.js 파일에서 serviceWorkerRegistration.register()를 호출해야 한다.

PWA를 사용하기 위해서는 Service Worker를 등록해야 합니다. 이를 위해 아래와 같이 코드를 추가할 수 있습니다.

import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';

const firebaseConfig = {
  // Firebase 설정 정보
};

// Firebase 초기화
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);

// Service Worker 등록
if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      const registration = await navigator.serviceWorker.register('/firebase-messaging-sw.js');
      messaging.useServiceWorker(registration);
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    } catch (err) {
      console.log('ServiceWorker registration failed: ', err);
    }
  });
}

// 알림 권한 요청 및 토큰 발급
async function requestPermission() {
  console.log('권한 요청 중...');

  const permission = await Notification.requestPermission();
  if (permission === 'denied') {
    console.log('알림 권한 허용 안됨');
    return;
  }

  console.log('알림 권한이 허용됨');

  const token = await getToken(messaging, {
    vapidKey: 'BBuoQiK6Hci6-fWBqgcIAn-a8Nzc7kF1XVpkCKfHINcvckb-u3sz8eSrsbtns2WjrXZ9bxs7j0DCsNtkNIiqjHc',
  });

  if (token) console.log('token: ', token);
  else console.log('Can not get Token');

  // 알림 수신 처리
  onMessage(messaging, (payload) => {
    console.log('메시지가 도착했습니다.', payload);
    // ...
  });
}

requestPermission(); 

위 코드에서 /firebase-messaging-sw.js는 Service Worker 파일이 위치한 경로를 의미한다. 이 경로는 실제 프로젝트에서 사용하는 경로에 맞게 변경해주어야 한다. 또한, vapidKey는 Web Push에서 사용되는 Public Key 값으로 변경해야 한다.

 

[Backend] Spring Boot Java 에서 알림을 구현 단계 

  1. FCM 설정하기 Firebase Console에서 프로젝트를 생성하고, FCM을 설정한다.
  2. Firebase Admin SDK 연동하기 Firebase Admin SDK를 이용하여 FCM을 사용할 수 있다. 이를 위해서는 Firebase Console에서 서비스 계정을 생성하고, 해당 계정의 인증 정보를 이용하여 Firebase Admin SDK를 초기화한다.
  3. FCM API 구현하기 Spring Boot Java 백엔드에서는 FCM API를 구현하여 Push Notification을 보낸다. 이를 위해서는 Firebase Admin SDK의 Messaging 클래스를 이용한다.
  4. 알림 목록 API 구현하기 알림 목록을 조회하고 관리하기 위해서는 백엔드에서 알림 관련 데이터를 저장하고, 필요한 API를 구현해야 한다. 예를 들어, 알림 목록을 조회하는 API를 구현하면, 프론트엔드에서 해당 API를 호출하여 알림 목록을 가져올 수 있다. 이때 백엔드에서는 데이터베이스를 이용하여 알림 정보를 저장하고, RESTful API를 구현하여 프론트엔드와 통신합니다. 또한, 알림을 클릭하면 해당 알림으로 이동할 수 있는 링크를 제공하기 위한 API도 구현할 수 있다. 이렇게 구현된 API를 이용하여, 사용자가 알림을 클릭하면 백엔드에서 해당 알림에 대한 처리를 수행하고, 필요한 화면으로 이동시키는 것이 가능한다.

 

구현 단계 자세히 보기

 

1. FCM 설정하기

 

파이어베이스 프로젝트 생성

  1. Firebase에 로그인하고 프로젝트를 연다
  2. 개요 페이지에서 앱 추가를 클릭한다
  3. 웹 앱에 Firebase 추가를 선택한다
  4. 스니펫을 복사하여 애플리케이션 HTML에 추가한다

💡 SDK 설정 및 구성 (Firebase SDK snippet) 을 잘 확인해야 한다

프로젝트 설정에서 일반탭에서 생성한 앱에 대한 정보를 확인이 가능하며, FCM은 클라우드 메시징 탭에서 확인 가능하다.

 

 

비밀 키 파일 생성

생성한 프로젝트 페이지 > 설정 > 서비스 계정 항목

 

프로젝트로 생성된 비밀키를 이동

원래 깃허브와 같이 공개된 장소에 이 파일을 같이 올리는 것은 안전하지 않다고 한다

이번 프로젝트에서는 json 파일로 생성된 비밀 키를 resources 디렉토리에 저장했다 

resources/firebase/greencherry-notice.json

{
  "type": "service_account",
  "project_id": "greencherry-notice",
  "private_key_id": "",
  "private_key": "",
  "client_email": "",
  "client_id": "",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-qjueq%40greencherry-notice.iam.gserviceaccount.com"
}

 

 

2. Firebase Admin SDK 연동하기

 

Dependency 설정

 

build.gradle

implementation 'com.google.firebase:firebase-admin:9.1.1'

 

application.yml

firebase:
  path: firebase/greencherry-notice.json

 

FCM 초기화

어플리케이션이 시작될 때 Firebase 프로젝트에 앱을 등록해줘야 한다.

(두 번 등록 되면 에러가 나므로 시작할 때마다 초기화 해준다)

 

commons/NoticeInitializer.java

@Slf4j
@Component
public class NoticeInitializer {

    @Value("${firebase.path}")
    private String path;

    @PostConstruct
    public void init(){
        ClassPathResource resource = new ClassPathResource(path);

        try(InputStream is = resource.getInputStream()) {
            FirebaseOptions options = FirebaseOptions.builder()
                    .setCredentials(GoogleCredentials.fromStream(is))
                    .build();

            if(FirebaseApp.getApps().isEmpty()){
                FirebaseApp.initializeApp(options);
                log.info("Firebase application init");
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }
}

 

 

 

3. FCM API 구현하기 (메세지 보내기)

메세지를 보낼 때 필요한 요소는 2가지이다

1. 누구한테 보낼지

 토큰과 토픽 방식으로 보낼 수 있는데, 토큰을 사용했다

2. 어떤 정보를 보낼지

 

아래와 같이 Message를 보내는 별도의 서비스를 만들었다

NotificationRequest에는 받을 상대의 토큰값, 푸시될 알림 메세지와 제목을 가지고 있고

사용하진 않지만, 이미지도 지정해서 넣어줄 수 있다

 

service/NoticeService

public void sendNotice(List<String> tokens){
        for (String token : tokens) {
            Message message = Message.builder()
                    .setToken(token)
                    .putData("title", "제목")
                    .putData("body", "내용")
                    .setWebpushConfig(WebpushConfig.builder().putHeader("ttl", "1000")
                            .setNotification(new WebpushNotification("title", "body"))
                            .build())
                    .build();

            FirebaseMessaging.getInstance().sendAsync(message);
        }
    }

 

 

 

더 자세한 코드를 확인하실 분은  아래 링크 참고 부탁드립니다

https://github.com/tandamzi/GreenCherry

 

 

 

참고

 

Web Push | React + FCM 구현하기 (feat. pwa, service worker)

웹에서도 네이티브 앱처럼 푸시 알람을 받을 수 있습니다. 웹의 사용성을 네이티브 앱처럼 개선하기 위해서 나온 기술인 PWA(Progressive Web Application)을 활용하면 가능합니다.PWA는 Progressive Web Applica

velog.io

 

FCM, Spring Boot를 사용하여 웹 푸시 기능 구현하기

Firebase firebase Firebase는 웹과 모바일 개발에 필요한 기능을 제공하는 BaaS(BackEnd as a Service) 이다. 백엔드 서버의 인프라들을 제공해주고 많은 기능들을 지원한다. 머신러닝 사용자 인증 파일 저장

velog.io

 

 

📮개인 공부를 위한 공간으로 틀린 부분이 있을 수도 있습니다.📮

문제점 및 수정 사항이 있을 시, 언제든지 댓글로 피드백 주시기 바랍니다.

'TIL' 카테고리의 다른 글

Redis ?  (0) 2023.05.26
FCM(Firebase Cloud Messaging)  (0) 2023.05.26
[JPA] OSIV와 성능 최적화  (0) 2023.03.22
[JPA] API 개발 고급 정리  (1) 2023.03.21
[JPA] 페이징과 한계돌파  (0) 2023.03.20

[JPA] OSIV와 성능 최적화

개발의 숩
|2023. 3. 22. 00:18

Open Session In View : 하이버네이트

Open EntityManager In View : JPA

(관례상 OSIV라 한다)

 

OSIV ON

  • spring.jpa.open-in-view : true 기본값

 

이 기본값을 뿌리면서 애플리케이션 시작 시점에 warn로그를 남기는 것은 이유가 있다.

OSIV 전략은 트랜잭션 시작처럼 최초 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지한다. 그래서 지금까지 View Template이나 API 컨트롤러에서 지연 로딩이 가능했던 것이다.

지연 로딩은 영속성 컨텍스트가 살아있어야 가능하고, 영속성 컨텍스트는 기본적으로 데이터베이스 커넥션을 유지한다. 이것 자체가 큰 장점이다.

그런데 이 전략은 너무 오랜시간동안 데이터베이스 커넥션 리소스를 사용하기때문에, 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있다. 이것은 결국 장애로 이어진다.

예를 들어서 컨트롤러에서 외부 API를 호출하면 외부 API 대기 시간만큼 커넥션 리소스를 반환하지 못하고, 유지해야 한다.

 

 

 

 

OSIV OFF

  • spring.jpa.open-in-view : false OSIV 종료

 

OSIV를 끄면 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환한다. 따라서 커넥션 리소스를 낭비하지 않는다.

OSIV를 끄면 모든 지연로딩을 트랜잭션 안에서 처리해야 한다. 따라서 지금까지 작성항 많은 지연 롣딩 코드를 트랜잭션 안으로 넣어야 하는 단점이 있다. 그리고 view template에서 지연로딩이 동작하지 않는다. 결론적으로 트랜잭션이 끝나기 전에 지연 로딩을 강제로 호출해 두어야 한다.

 

 

 

커멘트와 쿼리 분리

실무에서 OSIV를 끈 상태로 복잡성을 관리하는 좋은 방법이 있다.

바로 Command와 Query를 분리하는 것이다. 보통 비즈니스 로직은 특정 엔티티 몇개를 등록하거나 수정하는 것이므로 성능이 크게 문제가 되지 않는다. 그런데 복잡한 화면을 출력하기 위한 쿼리는 화면에 맞추어 성능을 최적화 하는 것이 중요하다. 하지만 그 복잡성에 비해 핵심 비즈니스에 큰 영향을 주는 것은 아니다.그래서 크고 복잡한 애플리케이션을 개발한다면, 이 둘의 관심사를 명확하게 분리하는 선택은 유지보수 관점에서 충분히 의미있다

 

단순하게 설명해서 다음처럼 분리하는 것이다

  • OrderService
    • OrderService : 핵심 비즈니스 로직
    • OrderQueryService : 화면이나 API에 맞춘 서비스( 주로 읽기 전용 트랜잭션 사용)

보통 서비스 계층에서 트랜잭션을 유지한다. 두 서비스 모두 트랜잭션을 유지하면서 지연 로딩을 사용할 수 있다.

 

참고 : OSIV에 관해 더 깊이 알고 싶으면 자바 ORM 표준 JPA 프로그래밍 13장 웹 애플리케이션과 영속성 관리를 참고하자

 

 

 

OSIV ,,? 아직 잘 모르겠다 어지럽다

 

[JPA] API 개발 고급 정리

개발의 숩
|2023. 3. 21. 11:52

정리

엔티티 조회

  • 엔티티 조회해서 그대로 반환 : V1
  • 엔티티 조회 후 DTO로 변환 : V2
  • 페치 조인으로 쿼리 수 최적화 : V3
  • 컬렉션 페이징과 한계 돌파 : V3.1
    • 컬렉션은 페치 조인시 페이징이 불가능
    • ToOne관계는 페치 조인으로 쿼리 수 최적화
    • 컬렉션은 페치 조인 대신에 지연 로딩을 유지하고, hibernate.default_batch_fetch_size, @BatchSize로 최적화
  • DTO 직접 조회
    • JPA에서 DTO를 직접 조회 : V4
    • 컬렉션 조회 최적화 - 일대다 관계인 컬렉션은 IN 절을 활용해서 메모리에 미리 조회해서 최적화 : V5
    • 플랫 데이터 최적화 - JOIN 결과를 그대로 조회 후 애플리케이션에서 원하는 모양으로 직접 변화 : V6

 

 

권장 순서

  1. 엔티티 조회 방식으로 우선 접근
    1. 페치조인으로 쿼리 수를 최적화
    2. 컬렉션 최적화
      1. 페이징 필요 hibernate.default_batch_fetch_size 로 최적화
      2. 페이징 필요 X → fetch join 사용
  2. 엔티티 조회 방식으로 해결이 안되면 DTO 조회 방식 사용
  3. DTO 조회 방식으로 해결이 안되면 NativeSQL or 스프링 JdbcTemplate

 

참고 : 엔티티 조회 방식은 페치 조인이나, hibernate.default_batch_fetch_size , @BatchSize r같이 코드를 거의 수정하지 않고, 옵션만 약간 변경해서, 다양한 성능 최적화를 시도할 수 있다. 반면에 DTO를 직접 조회하는 방식은 성능을 최적화하거나, 성능 최적화 방식을 변경할 때 많은 코드를 변경해야 한다.

 

 

 

참고 : 개발자는 성능 최적화와 코드 복잡도 사이에서 줄타기를 해야 한다. 항상 그런 것은 아니지만, 보통 성능 최적화는 단순한 코드를 복잡한 코드로 몰고간다. 엔티티 조회 방식은 JPA가 많은 부분을 최적화 해주기 떄문에, 단순한 코드를 유지하면서, 성능을 최적화 할 수 있다. 반면에, DTO 조회 방식은 SQL을 직접 다루는 것과 유사하기 때문에, 둘 사이에 줄타기를 해야 한다.

 

 

DTO 조회 방식의 선택지

  • DTO로 조회하는 방법도 각각 장단이 있다. V4,V5,V6에서 단순하게 쿼리가 1번 실행된다고 V6이 상항 좋은 방법인 것은 아니다
  • V4는 코드가 단순하다. 특정 주문 한 건만 조회하면 이 방식을 사용해도 성능이 잘 나온다. 예를 들어서 조회한 Order 데이터가 1건이면 OrderItem을 찾기 위한 쿼리도 1번만 실행하면 된다.
  • V5는 코드가 복잡하다. 여러 주문을 한꺼번에 조회하는 경우에는 V4 대신에 이것을 최적화한 V5 방식을 사용해야 한다. 예를 들어서 조회한 Order 데이터가 1000건인데, V4 방식을 그대로 사용하면, 쿼리가 총 1 + 1000번 실행된다. 여기서 1은 Order 를 조회한 쿼리고, 1000은 조회된 Order의 row 수다. V5 방식으로 최적화 하면 쿼리가 총 1 + 1번만 실행된다. 상황에 따라 다르겠지만 운영 환경에서 100배 이상의 성능 차이가 날 수 있다.
  • V6는 완전히 다른 접근방식이다. 쿼리 한번으로 최적화 되어서 상당히 좋아보이지만, Order를 기준으로 페이징이 불가능하다. 실무에서는 이정도 데이터면 수백이나, 수천건 단위로 페이징 처리가 꼭 필요하므로, 이 경우 선택하기 어려운 방법이다. 그리고 데이터가 많으면 중복 전송이 증가해서 V5와 비교해서 성능 차이도 미비하다.

 

보통은 페치 조인 , batch size 설정을 하면 해결이 된다 

[JPA] 페이징과 한계돌파

개발의 숩
|2023. 3. 20. 13:26

컬렉션을 페치 조인하면 페이징이 불가능하다.

  • 컬렉션을 페치조인하면 일대다 조인이 발생하므로 데이터가 예측할 수 없이 증가한다.
  • 일대다에서 일(1)을 기준으로 페이징을 하는 것이 목적이다. 그렇데 데이터는 다(N)를 기준으로 row가 생성된다.
  • Order를 기준으로 페이징하고 싶은데 다(N)인 OrderItem을 조인하면 OrderItem이 기준이 되어버린다.
  • 더 자세한 내용은 자바 ORM 표준 JPA 프로그래밍 - 페치 조인 한계 참고

이 경우 하이버네이트는 경고 로그를 남기고 모든 DB 데이터를 읽어서 메모리에서 페이징을 시도한다. 최악의 경우 장애로 이어질 수 있다.

 

 

한계 돌파

그러면 페이징과 컬렉션 엔티티를 함께 조회하려면 어떻게 해야할까 ?

지금부터 코드도 단순하고, 성능 최적화도 보장하는 매우 강력한 방법을 소개하겠다.

대부분의 페이징 + 컬렉션 엔티티 조회 문제는 이 방법으로 해결할 수 있다.

 

  • 먼저 ToOne 관계를 모두 페치조인한다. ToOne관계는 row 수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 않는다.
  • 컬렉션은 지연 로딩으로 조회한다.
  • 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size @BatchSize 를 적용한다.
    • **hibernate.default_batch_fetch_size : 글로벌 설정**
    • **@BatchSize : 개별 최적화 - 디테일하게 적용하고 싶을 때**
    • 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size만큼 IN 쿼리로 조회한다.
    jpa:
        hibernate:
          ddl-auto: create
        properties:
          hibernate:
            show_sql: true
            format_sql: true
            default_batch_fetch_size: 1000 #최적화 옵션
    
    • 개별로 설정하려면 @BatchSize를 적용하면 된다 (컬렉션은 컬렉션 필드에, 엔티티는 엔티티 클래스에 적용)
    장점
    • 쿼리 호출 수 가 1+N → 1+1로 최적화된다
    • 조인보다 DB 데이터 전송량이 최적화된다.이 방법은 각각 조회하므로 전송해야할 중복 데이터가 없다
    • Order와 OrderItem을 조인하면 Order가 OrderItem만큼 중복해서 조회된다.
    • 페치 조인 방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소한다.
    • 컬렉션 페치 조인은 페이징이 불가능하지만 이 방법은 페이징이 가능하다
    결론
    • ToOne 관계는 페치 조인해도 페이징에 영향을 주지 않는다. 따라서 ToOne관계는 페치조인으로 쿼리 수를 줄이고 해결하고, 나머지는 hibernate.default_batch_fetch_size 로 최적화하자
    참고: default_batch_fetch_size 의 크기는 적당한 사이즈를 골라야 하는데, 100 ~ 1000 사이를 선택하는 것을 권장한다. 이 전략은 SQL IN절 사용하는데, 데이터베이스에 따라 IN절 파라미터를 1000으로 제한하기도 한다. 1000으로 잡으면 한번에 1000개를 DB에서 애플리케이션에 불러오므로 DB에 순간 부하가 증가할 수 있다. 하지만 애플리케이션은 100이든 1000이든 결국 전체 데이터를 로딩해야 하므로 메모리 사용량이 같다. 1000으로 설정하는 것이 성능상 가장 좋지만, 결국 DB든 애플리케이션이든 순간 부하를 어디까지 견딜 수 있는지로 결정하면 된다.
    • 한번에 조회
    • 데이터가 최종 갯수만큼 나오는데 중복인 된 상태에서 조회가 된다
    • DB에서 애플리케이션으로 전부 전송하게 되어, 전송량 자체가 많아지게 된다
    • 용량이 많은 이슈
    public List<Order> findAllByMemberDelivery(int offeset, int limit) {
            return em.createQuery(
                            "select o from Order o"+
                                    " join fetch o.member m "+
                                    " join fetch o.delivery d ",Order.class)
                    .setFirstResult(offeset)
                    .setMaxResults(limit)
                    .getResultList();
        }
    
    • 데이터 전송량이 쿼리가 최적화되어서 전송되기때문에 용량이슈가 발생하지 않는다
    • 데이터는 중복없이 조회가 가능하다
    • 정확하게 필요한 데이터를 조회하게 된다.
    public List<Order> findAllByMemberDelivery(int offeset, int limit) {
            return em.createQuery(
                            "select o from Order o",Order.class)
                    .setFirstResult(offeset)
                    .setMaxResults(limit)
                    .getResultList();
        }
    
    • batch size 전략때문에 성능 최적화가 된 상태에서 데이터를 가져올 수 있다.
    • 아무래도 네트워크를 많이 타서 가져오게 되므로 fetch join을 미리 잡아놓고 가져오는게 효율적이다

 

 

'TIL' 카테고리의 다른 글

[JPA] OSIV와 성능 최적화  (0) 2023.03.22
[JPA] API 개발 고급 정리  (1) 2023.03.21
[JPA] N+1 문제  (0) 2023.03.19
[JPA] respository에 DTO 사용 ?  (0) 2023.03.18
[JPA] DTO 설계 이유  (0) 2023.03.18