Spring발행일 2025. 1. 4.원본 https://blog.naver.com/jword_/223714854459 ↗

Noti api 설계하기

Noti api 설계하기 — #개발자의도구들 🚨📝 📖 📒✏️💡🔍 AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 ...

#Spring#Naver Blog

#개발자의도구들

🚨📝 📖 📒✏️💡🔍

​

AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다

\* 현재 Spring x 프로젝트를 진행하고 있습니다. 전체 목차를 보시려면 여기를 눌러주세요.

\* 해당 프로젝트 이전에 공부한 내용을 보시려면 여기를 눌러주세요.

​

💡 Tips

Notification Api 설계하기

이미지

SNS 플랫폼의 핵심기능인 notification을 설계하고 안드로이드 앱에서 테스팅을 진행하였습니다 설계한 내용과 진행하면서 여러가지 깨달은 점들을 공유하고자 합니다.

고려한 것들

타임라인 api와 크게 다르지 않습니다. 그보다도 noti에서 중요한 개념들이 많이나와 시리즈 별로 정리를 진행할 예정입니다.

​

클라이언트 시나리오

아래는 noti api를 만들때 클라이언트의 예상 행동을 정리한 것 입니다.

javascript 코드 예제
                                    client

1. 인플루언서 A로부터 noti생성
- User A는 1만명의 팔로워를 보유한 인플루언서이다.
- A는 오늘 낮에 "친구들과 깜짝파티. 너무 즐겁고 행복한 순간을 만들어줘서 고마워친구들아. 항상행복하자 우리"
 라는 메세지와 여러 파티사진이 담긴 포스터를 발행하였다.

2. 팔로워 B로 부터 noti 요청
- User B는 A의를 팔로우중인 A의 팬이다. 그는 오늘 점심시간에 밥을 먹으며
X를 보던 중이었는데 UserA가 새로 발행한 "친구들과 깜짝파티. 너무 즐겁고 행복한 순간을..."이라는
noti를 받게된다.

noti는 크게 두가지 로직을 고려해야합니다. 그리고 기본적으로 팔로워들에게 전송되어야 하죠.

  • 저장
  • 조회

​

저장의 경우는 포스팅, 팔로우, 좋아요 등의 api요청, 조회의 경우는 유저 로그인시 쌓여있는 noti를 받아와야 합니다.

​

실시간 처리? ⭐

처음 방향을 잡을때 많이 헷갈렸던 것인데, 노티를 실시간으로 처리해야 한다고 생각했습니다. 하지만, 실제로 실시간 처리 통신은 속도가 매우 민감한 환경(인터넷 방송, 게임) 등에서만 필요한 것이고, 노티는 그 정도로 민감하게 반응할 필요가 없습니다.

​

이미지

위는 노티 구조를 도식화하였습니다. 초창기 진행시에는 따로 서버를 분리하지 않았었습니다. 이후에는 분리를 진행하였습니다.

이미지

위의 컨트롤러 구조가 notiServer로 들어 갔다고 보시면 됩니다.

​

양방향 스크롤링

앞서 타임라인 처럼, 노티 역시 양방향 스크롤링이 가능해야합니다. 이 부분은 타임라인에서 자세히 다룬바 있으므로 해야한다는 것만 언급하고 넘어가고자 합니다.

Impl

데이터 베이스

노티의 경우 DB에 다음과 같은 정보가 들어가야 합니다.

  • 생성일자
  • 발행자
  • 수신자
  • 종류(좋아요, 팔로우, 게시글)
  • 게시글 번호(게시글인 경우)
javascript 코드 예제
                                    CREATE TABLE notifications(
    id BIGINT PRIMARY KEY Auto_increment,
    publisher_id BIGINT,
    receiver_id BIGINT,
    created_at datetime,
    noti_type int,
    board_id int,
)

CREATE INDEX idx_noti_receiver
ON notifications(receiver_id)

서비스

클라이언트의 요구사항에 따라 다음과 같은 정보가 추가로 필요합니다. 이는 frontServer에서 처리합니다.

이미지

저는 UI에 표시된 정보들을 추가로 넘겼습니다.

  • 프로필 이미지 URL
  • 발행자 이름
  • 내용

​

이를 위해서 프론트 측에서는 발행자 이름, 프로필 이미지, 게시글 내용을 찾아서 넘겨줍니다.

javascript 코드 예제
                                    @Service
class NotificationApiService(
    private val webConfig: WebConfig,
    private val circuitBreakerRegistry: CircuitBreakerRegistry,
    private val userRepository: UserRepository,
    private val boardRepository: BoardRepository,
    private val messageSource: MessageSource
)

현재 user랑 board의 경우는 front server와 연동되어있습니다. 가장 기본이되는 정보들이라 따로 나누지 않으려고 합니다.

javascript 코드 예제
                                    @CircuitBreaker(
        name = "notificationApiCircuitBreaker",
        fallbackMethod = "getInitFallbackMethod"
    )
    fun fetchInitAll(receiverId: Long, language: String): List<NotificationGetResult>  {
        val notiServerWebClient = webConfig.createWebClient(baseUrl, language)
        val response = notiServerWebClient.post()
            .uri { uriBuilder: UriBuilder ->
                uriBuilder
                    .path("/notification/init")
                    .build()
            }
            .bodyValue(
                receiverId
            )
            .retrieve()
            .onStatus({ it.isError }) { clientResponse ->
                // HTTP 상태코드가 에러일 때 처리
                clientResponse.bodyToMono(String::class.java).flatMap { errorBody ->
                    // 이곳에서 에러 내용 로깅
                    logger.error {"HTTP Error: ${clientResponse.statusCode()} / Body: $errorBody"}
                    throw RuntimeException("HTTP Error: ${clientResponse.statusCode()} - $errorBody")
                }
            }
                .bodyToMono(NotificationGetServerResponse::class.java)
            .block()

        val results = response?.serverResults ?: emptyList()
        logger.info { "response: $results" }

        return results.map {
            // make message by result's parameter
            val username = findUserNameById(it.publisherId)
            val imgYrl = findUserImgUrlById(it.publisherId)
            val targetBoard = it.targetBoardId?.let {
                id -> boardRepository.findById(id).orElse(null)
            }

            val message = makeMessage(it, username, language, targetBoard)
            NotificationGetResult.of(it, username, imgYrl, message)
        }
        // return noti server response
    }

메세지 조합 과정은 return 부터 보시면 됩니다. makeMessage는 다국어 처리 관련 메서드라 일단 여기서는 다루지 않겠습니다.

이미지

이런식으로 서버로 부터 받아온 DTO에는 key가 들어있고 해당 key값을 통해 DB나 다른 서버로 부터 정보를 받아와서 최종적으로 전달합니다.

​

이는 매우 자주 사용되는 패턴으로 기억해 두었습니다.

계속 반복되는 패턴들이라 일단은 여기까지 설명하고 다음글에서 UI구성 부분을 다뤄보겠습니다.