리디의 앱 개발자로 입사하면서

소개

안녕하세요. 리디의 제품 센터에서 모바일 앱 엔지니어로 근무하고 있는 문호현이라고 합니다. 리디 제품 센터는 리디북스, 리디셀렉트 등 리디의 주요 제품을 담당하고 있는 곳으로 저는 이 제품들의 모바일 앱을 개발하는 업무를 맡고 있습니다.

간단히 제 소개를 드리자면, 리디에 입사하기 전 저는 미국에서 대학 생활을 하며 틈틈히 관심 있는 분야에 대해 개발 경험을 쌓고 있었습니다. 하지만, 이것만으로는 무언가 부족하다는 느낌이 들었고, 마침 코로나 19로 인해 대학 강의가 온라인이 되면서 학교 생활에 대한 불만이 생겼습니다. 그래서 차라리 휴학을 하고 제대로 일을 해보자는 결심을 하게 되어 리디에 입사하게 되었습니다.

이 글에서는 리디에 앱 개발자로 입사하고 느낀점과 진행한 테스크를 소개해보고자 합니다.

온보딩과 문화

입사 한 첫 날부터 적응 할 때까지 신규 입사자의 적응을 돕기 위해 회사에서 많은 노력을 하는 것이 여러 부분에 걸쳐 느껴졌습니다. 입사 당일에 진행한 오티와 팀별 오티까지, 간단한 오티들이었지만 회사와 각 팀이 하고 있는 업무에 대해 조금 더 잘 알 수 있게 되었습니다. 뿐만 아니라, 입사하고 몇 주 동안 HRBP 팀을 비롯해 같이 일하게 될 팀원들과 점심과 커피를 간단하게 즐기며 조금 더 가까워질 수 있었고, 사내 싱크업이나 팀별 싱크업 미팅과 같은 각종 미팅을 통해 각자의 업무를 조금 더 수월하게 공유하고 개개인에 대해 조금 더 잘 알 수 있었습니다.

리디의 사내 문화 역시 만족스러웠는데요. 우선, 개인이 원하고 성장하고 싶은 방향을 고려해서 테스크 배정을 한다는 것이 좋았습니다. 또, 강압적이지 않고 적당히 유연한 분위기는 개발 퍼포먼스를 향상 시켜주었습니다. 특히, 각 팀의 리드 개발자분들이 수직적인 관계보다는 주니어 개발자들의 입장에서 바라보면서 조금 더 편할 수 있게 해주려는 배려가 온보딩하는데에 큰 도움을 주었습니다. 근무 시간만 충족한다면, 출퇴근 시간이 자유롭다는 것이나 각종 장비, 운동비 지원부터 코로나 19로 인한 점심 도시락 지원까지 복지도 인상 깊었습니다.

테스크

이제 3개월의 수습기간동안 제가 어떤 업무에 집중했는지 본격적으로 소개해보려고 하는데요. 리디만의 좋은 문화와 많은 분들의 도움속에 회사 생활에 자연스럽게 녹아들다보니 업무 역시 보다 집중해서 할 수 있었습니다.

joining-ridi-app-engineer

리디북스 앱은 현재 여러 개의 탭으로 구성되어 있고, 이 탭들은 Swift와 React Native를 통해 조금씩 다르게 구현되어 있습니다. 그 중에서 저는 제가 가장 좋아하고 잘 할 수 있는 Swift 구현부를 중점으로 업무를 진행했습니다.

처음으로 주어진 업무는 리디북스의 앱을 전체적으로 분석하고 도식화 해보는 것이었고, 그 후에는 한 동안 버그를 수정하는데 집중했습니다. 사용자가 책장을 임의로 정렬하거나 책을 다운로드하면 책장의 정렬이 흐트러지는 버그부터 다음 책으로 넘어가면 타이머가 연동이 되지 않는 버그 등 다양한 버그 수정을 통해 앱 전반의 코드를 이해할 수 있었습니다.

joining-ridi-app-engineer

그 후에는 앱에서 비중이 있거나 앱을 사용하면 직접적으로 티가 나는 부분을 본격적으로 작업하기 시작했는데요. 내 서재와 구매 목록 탭을 통합하는 작업을 비롯해, 백엔드 팀에서 새롭게 설계한 API에 맞게 통신부를 구현하거나 데이터를 모델링하는 작업을 했습니다. 이 중에서도 뷰어의 독서 노트 동기화에 필요한 API 전환 및 레거시 코드 개선이 가장 기억에 남아, 이 부분을 어떻게 개발했는지 조금 더 자세히 소개해보려 합니다.

import HTTPURLKit

struct Annotations {
  struct FetchResponse: Decodable { ... }

  struct FetchRequest: Requestable, Encodable {
    typealias Parameters = EmptyParameters
    typealias ResponseBody = FetchResponse
    
    ...
    
    var url: URL { ... }
    var validations: [Validation] { ... }
  }

  struct ChangeRequest: Requestable, Encodable {
    typealias Parameters = Self
    typealias ResponseBody = EmptyResponse
    
    ...
    
    var url: URL { ... }
    var httpMethod: URLRequest.HTTPMethod { ... }
    var validations: [Validation] { ... }
    var parameterEncodingStrategy: ParameterEncodingStrategy? { ... }
  }
}

독서 노트 동기화를 위해 설계한 API 통신은 다음과 같은 response와 request 형태를 갖습니다. HTTPURLKit은 URLKit에 포함된 패키지로 리디에서 자체 개발 되었는데요. 애플의 프레임워크나 Alamofire 같은 써드파티 프레임워크가 업데이트 되어도 최대한 영향을 받지 않기 위해 만들어졌습니다.

사용 법은 꽤 심플합니다. 여느 Swift 데이터 모델링과 마찬가지로 API에 맞게 Codable을 준수하는 response나 request 구조를 작성합니다. 여기에 추가적으로 response나 request 구조체가 Requestable을 준수하도록 하면 되는 것인데요. Parameters, ResponseBody 등을 지정하고 url, httpMethod와 같은 몇 가지 변수를 설정하면 됩니다.

import RealmSwift

class Annotation: Object { ... }

@objc(AnnotationChunk)
class AnnotationChunk: NSObject, NSCoding, Codable { ... }
import RealmSwift

@objcMembers
class NewAnnotation: Object, Codable { ... }

리디북스 앱은 로컬에 독서 노트 데이터를 저장하기 위해 NSObject도 사용했었고, Realm의 오브젝트도 사용했었는데요. 앞으로는 이를 Realm으로만 관리하기 위해 통합 작업을 진행했습니다.

기존에는 두 종류의 데이터를 호환해주기 위해 Realm 오브젝트인 Annotation과 NSObject인 AnnotationChunk를 혼용해서 사용했다면, 이번 작업을 통해 NewAnnotation이라는 Realm 오브젝트이자 Swift의 Codable을 준수하는 객체만 사용할 예정입니다.

NewAnnotation이 Realm 오브젝트인 동시에 Codable을 준수하기 때문에 위에서 설계한 API 통신의 response나 request에도 바로 사용할 수 있고, Realm 데이터베이스에도 바로 접근할 수 있었는데요. 여기서 조심할 점은 Realm은 하나의 쓰레드에서만 작동 할 수 있기 때문에 NewAnnotation 객체를 사용할 때 managed, unmanaged 객체를 구분해서 사용해야 합니다.

import RxHTTPURLKit

final class AnnotationSynchronizer {
  ...
  
  func synchronize(completion: @escaping ((_ syncedAnnotations: [NewAnnotation]?) -> Void)) {
    Session.ridiAPIShared.rx.request(
        Annotations.ChangeRequest(...)
      )
      .subscribe(onSuccess: { _ in
        ...
      }, onError: { _ in 
        ...
      }
  }
  
  ...
}

실질적인 API 통신은 Rx 방식을 따릅니다. 특별할것 없는 방식이지만, URLKit의 일부인 RxHTTPURLKit를 import 해서 보다 편하게 API 통신을 합니다. 이렇게 해서 완성된 일련의 동기화 과정은 sychronize 함수를 독서 노트 동기화가 필요한 시점마다 호출해서 진행하면 됩니다.

import RealmSwift

extension Realm {
  ...
  
  static func annotationConfiguration(fileUrl url: URL, forBook book: Book) -> Configuration {
    var configuration = Configuration()

    ...

    configuration.migrationBlock = RealmMigrationBlocks.annotationRealmMigrationBlock(book)

    ...
  }
}
import RealmSwift

struct RealmMigrationBlocks {
  ...

  static func annotationRealmMigrationBlock(_ book: Book) -> (Migration, UInt64) -> Void {
    return { migration, oldSchemaVersion in
      ...

      if oldSchemaVersion < 7 { ... }
    }
  }

  ...
}

API가 변경되면서 받아오는 데이터의 종류가 달라졌기 때문에, 데이터베이스의 구조도 달라졌습니다. 그러다보니, Realm 데이터베이스를 마이그레이션 하는 작업도 필요하게 되었는데요. Realm은 Core Data에 비하면 마이그레이션 작업 역시 정말 간단합니다.

Realm의 configuration 부분에 migrationBlock 형태로 마이그레이션에 필요한 코드를 넘기면 되는데요. 자세한건 설명해드리기 어렵지만, 간단히 말해 데이터베이스의 schemaVersion을 올리고 버전에 따라 실행해줘야 하는 마이그레이션 코드를 작성하면 됩니다.

joining-ridi-app-engineer

이렇게해서 개발이 완료된 feature은 리디 리포지토리에 Pull Request로 등록합니다. 그러면 팀의 리드 개발자가 리뷰를 진행하게 되고, 리뷰가 완료되면 개발 브랜치에 merge가 됩니다.

joining-ridi-app-engineer

이번 독서 노트 동기화 작업은 겉으로는 크게 드러나지 않지만, 기존과 같은 결과물을 나타내면서도 내부적으로 많은 개선이 있었다는데 만족했습니다.

마무리

저는 이제 리디를 비롯해 연구실, 스타트업, 동아리 등에서 앱 개발자로 근무를 해보게 되었는데요. 작다면 작은 경험들이었지만 이 경험들을 하며 개발자는 '적합한 대우와 지속적인 성장 가능성이 공존하는 환경'에 있는 것이 중요하단걸 느끼게 되었습니다. 그리고 리디는 이 환경을 꽤나 잘 갖춘 기업이라는 생각을 하게 되었습니다.

온보딩 기간동안 생각보다 많은 것을 느끼고 알게 되었지만, 사실 저는 이제 막 걸음마를 뗀 개발자로 앞으로의 리디 생활이 더욱 기대됩니다. 읽어주셔서 감사합니다!

P.S. 이 글은 리디 홈페이지에도 업로드 되어있습니다.

관련 글