본문 바로가기
iOS/개발

[iOS] SOPT - 푸시 알림 딥링크 라우팅 개발 여정 1

by 바등쪼 2024. 1. 5.

오늘은 IT 동아리 SOPT의 공식앱인 SOPT를 개발하며 했던 고민들과 이렇게 개발한 이유에 대해 적어보려고 합니다!

 

주제는 바로 푸시 알림 딥링크 라우팅입니다.

 

푸시 알림 딥링크란 무엇일까?

아이폰을 사용하다보면 카톡이 오거나 기타 앱에서 광고 같이 알림 센터에서 짧게 팝업으로 표시하는 메시지가 있는데 이것을 푸시 알림이라고 합니다.

푸시 알림 예시 (출처: https://developer.apple.com/notifications/)

 

SOPT 앱에서도 이런 푸시 알림에 대한 니즈가 생기기 시작했습니다.

 

특히, 푸시 알림이 유용한 이유는 저 알림을 터치했을 때 개발자가 원하는 위치의 화면으로 이동시키는 경험을 제공할 수 있기 때문입니다.

카카오톡 푸시 알림을 터치하면 해당 메시지가 있는 채팅방으로 바로 이동하는 것처럼 말이죠!

 

이것을 저는 푸시 알림 딥링크 라우팅이라고 부르고 있습니다.

푸시 알림의 페이로드에 딥링크(URL과 같은 화면 단위 주소)를 넣어서 보내고 클라이언트는 이 딥링크를 파싱하여 목적지 뷰까지 라우팅을 시켜주는 것입니다!

 

이 기능을 통해 사용자가 직접 화면을 여러번 터치해서 특정 위치로 이동할 때보다 훨씬 짧은 시간과 뎁스로 원하는 정보가 있는 화면까지 도달하도록 하여 사용성을 크게 높일 수 있습니다.

 

구현 목표

시뮬레이터 구동 화면

 

이렇게 푸시 알림을 터치하면 정해진 딥링크를 따라 화면을 이동시키는 것이 최종 목표입니다.

 

 

TF의 형성

현재 SOPT 앱은 SOPT Makers라는 조직에서 개발을 진행하고 있습니다.

 

SOPT에 없던 새로운 가치를 프로덕트를 통해 만들어 간다는 이념으로 만들어진 정식 기구이며 약 50명의 인원이 함께 SOPT를 위한 앱, 웹 프로덕트를 개발하고 있습니다.

 

SOPT 앱에 대해서는 현재 2개의 팀이 각각 10명씩 구성되어 개발을 진행하고 있습니다. 운영프로덕트 팀과 앱 팀으로 나누어져 있고 앱팀에서는 앱의 플랫폼적인 측면을 위주로 개발하고 있으며 저는 이 앱팀에 속해있습니다.

 

다시 본론으로 돌아와서!

이렇게 많은 인원이 공동의 목표를 위해 개발을 진행하고 있는데 "푸시 알림"은 단순히 앱팀에서만 필요한 기능이 아닙니다.

앱이 여러 웹 서비스를 연결해주는 플랫폼으로의 역할을 하고 있기 때문입니다.

 

예를 들어 SOPT Playground라는 웹을 개발하는 팀들이 있는데, 이 웹에서도 새로운 스터디 모집 글이 올라왔을 때 앱으로 푸시 알림을 발송하여 사용자들을 유입시키고 싶을 수 있습니다. 또는 커뮤니티에 댓글이 달렸을 때도 푸시알림은 유용하게 사용될 수 있습니다.

 

정리하면, 푸시 알림은 제가 속한 앱 팀에서만 사용될 것이 아니라 Makers 조직 전체의 팀에서도 접근할 수 있는 기능이어야 합니다.

 

따라서 팀의 경계를 넘어 이 니즈에 공감하는 사람들이 모여 함께 푸시 알림 피쳐를 개발해야 하는 조직 차원의 마일스톤이 생긴 것입니다.

 

 

전체의 목표를 달성하기 위해 자원자들이 모였고 "알림 TF"가 꾸려지게 되었습니다!

 

저는 클라이언트 대표로 TF에 참여하여 개발을 진행했습니다.

 

 

스펙 결정

본격적인 개발에 앞서 푸시 알림 기능에 대한 정확한 스펙에 대한 결정이 필요했습니다.

 

알림 TF의 개발자분들과 PM분들이 함께 논의를 하며 세부적인 스펙에 대한 결정을 내리고 문서화 했습니다.

 

문서의 일부..

 

 

또한 iOS, 안드로이드 클라이언트 개발자들과 함께 이야기를 나누며 푸시 알림 페이로드에 들어가게 될 딥링크의 규격을 정했습니다.

 

 

흔히 볼 수 있는 URL 형태를 차용하여 다른 개발자 혹은 PM 들이 봤을 때 어떤 뷰로의 라우팅을 의미하는 지 알 수 있도록 했습니다.

 

Path와 Query로 나누어지며 Query에는 딥링크가 의미하는 뷰로의 라우팅에 필요한 데이터를 담게 됩니다.

 

예를 들어 솝탬프 피쳐의 현재 기수 랭킹 뷰의 경우 현재 기수가 몇번째 기수인지에 대한 데이터가 필요한데 이 때 currentGeneration=33과 같은 형태로 쿼리가 주어지면 33기수라는 것을 알 수 있습니다.

 

이 표는 백엔드 개발자분들께 공유하여 푸시 알림 페이로드에는 저 규격의 딥링크 만이 담겨져 오도록 싱크를 맞췄습니다.

 

딥링크 트리

 

추가로 각 딥링크는 Path를 기준으로 트리 구조를 형성하도록 설계했습니다.

이 개념은 추후에 딥링크를 파싱할 때도 사용됩니다.

 

모든 논의가 끝나고 클라이언트에서 받게되는 APNs 페이로드의 형태는 다음과 같도록 결론을 내릴 수 있었습니다.

{
    "Simulator Target Bundle" : "bundle id",
    "aps" : {
        "alert" : {
            "title" : "테스트 푸시알림",
            "body" : "랭킹을 확인해 보세요!",
        }
    },
    "category": "NOTICE",
    "deepLink" : "home/soptamp/entire-ranking",
    "id": "2133"
}

 

 

 

 

이제 사전 준비를 끝냈으니 클라이언트에서는 딥링크를 파싱하여 라우팅을 하는 로직을 구현하면 됩니다.

 

 

 

라우팅 로직 구현

사전 안내

  • 푸시 알림 발송을 위해서는 APNs Device Token이 필요한데 이 토큰의 발급과 앱 서버로의 전송은 이미 구현되어 있다고 가정합니다. 즉, 푸시 알림의 수신 자체는 이미 되고 있으며 라우팅 로직에 대한 구현이 이번 글의 주제입니다.
  • 현재 SOPT앱의 iOS 프로젝트는 링크 기반의 Coordinator 패턴이 사전에 적용되어 있습니다. (자세한 내용은 PR을 참고해 주세요!)
  • 즉, 화면 전환 로직은 Coordinator가 담당하고 있으며 이번 로직 구현에서는 이 Coordinator와의 상호작용을 통해 어떻게 하면 "잘" 그리고 "확장성"있는 로직을 구현할 수 있을지에 대한 고민에서 시작했습니다.

 

방향성

프로젝트에 적용된 코디네이터 패턴을 소개하는 아티클에서는 딥링크를 처리할 때 enum을 활용하여 딥링크를 선언하고 각 Coordinator에서 switch문을 연쇄적으로 걸어 라우팅을 이어나가는 방식을 제안하고 있었습니다.

public override func start(with option: DeepLinkOption?) {
    switch option {
    case .home:
        runHomeFlow()
    case .mypage:
        runMypageFlow()
    case .soptamp:
        runSoptampFlow()
    // 딥링크 뷰가 추가될 때마다 case 추가
    }
}

 

저도 처음에는 이 로직이 제일 먼저 떠오른 아이디어였습니다.

 

하지만, 딥링크과 연결된 뷰들은 앱 서비스가 커질수록 계속 증가할 것이고 이 때마다 enum에 case를 추가하고 연견된 모든 Coordinator에 switch문을 추가해야 합니다.

 

객체 지향의 관점에서 그리고 SOLID 원칙을 대입하여 생각했을 때 이 방식은 OCP에 위배되는 사례라고 판단했습니다.

수정에는 열려있고 확장은 어렵게 만드는 좋지 않은 구조라는 것이죠!

 

또한 코디네이터가 딥링크 로직까지 깊게 관여하게 되어 너무 많은 책임을 지게 됩니다.

 

SRP, OCP 측면에서 더 좋은 방법에 대해 고민을 시작했고 프로토콜과 다형성을 사용하여 딥링크 로직과 코디네이터를 최대한 분리하고 다형성을 가지는 객체들이 코디네이터와 협력하며 뷰를 이동시키는 구조를 떠올렸습니다.

 

초기 구조 설계 도식화

 

앞서 생각한 아이디어를 기반으로 기본 구조를 도식화를 했습니다.

 

푸시 알림이 도착하면 이를 NotificationHander가 수신하고 DeepLinkParser에게 파싱을 요청합니다.

파싱된 데이터는 DeepLinkComponents라는 객체(자료구조)가 담게 되고 AppCoordinator는 이 Components가 가진 딥링크 구현체들을 실행하여 뷰를 쌓는 구조입니다.

 

(여기서 딥링크 구현체는 프로토콜 다형성 가지는 메서드를 구현한 구현체를 의미합니다.)

 

이렇게 되면 Coordinator에는 switch문이 필요 없게 되고 딥링크 추가에 따른 enum 케이스 추가 작업도 필요 없어집니다.

Coordinator는 딥링크 구현체가 요청하는 화면 전환 작업만 맡아주면 됩니다.

 

세부적인 라우팅 로직은 각 딥링크 구현체가 역할을 맡게 되는 것입니다.

 

 

Class Diagram

기초 구조를 확정했으니 이제 세부적인 설계 구조를 클래스 다이어그램으로 그렸습니다.

딥링크 라우팅 모듈 클래스 다이어그램

 

각 요소(프로토콜, 클래스, 구조체)들에 대한 설명도 계속 이어집니다!

 

Sequence Diagram

딥링크 라우팅 모듈 시퀀스 다이어그램

 

푸시알림 딥링크 라우팅의 경우 실행 순서 또한 로직 이해에 중요할 것 같아서 시퀀스 다이어그램 또한 만들었습니다.

 

 

모든 로직을 구현할 때 마다 이렇게 다이어그램을 그리지는 않지만 이번에 구현한 라우팅 로직의 경우 추후에 새로운 iOS 개발 팀원이 들어오거나 다른 개발자가 새로 추가되는 딥링크 요구사항을 구현할 때 이해를 돕기 위해 문서화 차원에서 자세히 다이어그램을 그려보았습니다.

 

이 다이어그램들과 상세 설명은 Pull Reqeust에도 적어두어 팀원들과 함께 공유하고 피드백을 주고 받은 뒤 여러번 수정 작업을 거쳤습니다.

 

 

 

내용이 길어져 상세한 코드 설명은 다음 글로 이어집니다!!

 

 

댓글