안녕하세요. 리디의 제품 센터에서 모바일 앱 엔지니어로 근무하고 있는 문호현이라고 합니다. 리디 제품 센터는 리디북스, 리디셀렉트 등 리디의 주요 제품을 담당하고 있는 곳으로 저는 이 제품들의 iOS 앱을 Swift로 개발하는 업무를 맡고 있습니다.
간단히 제 소개를 드리자면, 리디에 입사하기 전 저는 미국에서 대학 생활을 하며 틈틈히 관심 있는 분야에 대해 개발 경험을 쌓고 있었습니다. 하지만, 이것만으로는 무언가 부족하다는 느낌이 들었고 코로나19를 계기로 학교를 벗어나서 새롭게 배웠던 것을 실무적으로 활용할 수 있는 기회를 만들어 보자는 결심을 하게 되어 리디에 입사하게 되었습니다.
이 글에서는 리디에 앱 개발자로 입사하고 느낀점과 진행한 테스크를 소개해보고자 합니다.
세분화된 온보딩과 자율적 문화
<회사와 동료 알아가기>
입사 한 첫 날부터 적응 할 때까지 신규 입사자의 적응을 돕기 위해 회사에서 많은 노력을 하는 것이 여러 부분에 걸쳐 느껴졌습니다. 회사, 제품센터, 팀 단위로 다양한 OT가 마련되어 있었고, 이를 바탕으로 전사적인 것부터 각 팀 단위까지 잘 파악할 수 있게 되었습니다. 또한, 입사 초기 몇 주 동안은 점심식사와 티타임을 통해 팀원들과 라포(rapport) 형성을 위한 충분한 시간을 회사로부터 지원받을 수 있었습니다.
매월 진행하는 사내 싱크업데이(r-sync)를 통해서 현재 리디가 나아가는 방향이나 각 본부 별 현황을 알 수 있었고, 제품센터 타운홀 미팅과 팀별 싱크업 미팅 등 다양한 세션을 통해 각자의 업무를 조금 더 활발하게 공유하고 구성원 개개인에 대한 이해도를 높일 수 있었습니다.
<리디의 자율적인 문화>
리디의 사내 문화 역시 만족스러웠는데요. 우선, 개인이 원하고 성장하고 싶은 방향을 고려해서 테스크 배정을 한다는 것이 좋았습니다. 예를들어, 저는 모바일 앱 엔지니어로 입사하게 되어 모바일 앱 개발 중에서도 React Native, iOS 네이티브, Android 네이티브 등 다양한 개발 선택지가 있었는데요. iOS 네이티브 앱 개발을 원해, Swift로 개발할 수 있는 테스크를 배정 받았습니다.
또, 강압적이지 않고 적당히 유연한 분위기는 개발 퍼포먼스를 향상 시켜주었습니다. 특히, 각 팀의 리드 개발자분들이 수직적인 관계보다는 주니어 개발자들의 입장에서 바라보면서 조금 더 편할 수 있게 해주려는 배려가 온보딩하는 데에 큰 도움을 주었습니다. 출퇴근 시간이 비교적 유연하다는 것이나 각종 장비, 운동비 지원부터 코로나19로 인한 점심 도시락 지원까지 복지도 인상 깊었습니다.
테스크
이제 3개월의 수습기간동안 제가 어떤 업무에 집중했는지 본격적으로 소개해보려고 하는데요. 리디만의 좋은 문화와 많은 분들의 도움속에 회사 생활에 자연스럽게 녹아들다보니 업무 역시 보다 집중해서 할 수 있었습니다.
리디북스 앱은 현재 여러 개의 탭으로 구성되어 있고, 이 탭들은 Swift와 React Native를 통해 조금씩 다르게 구현되어 있습니다. 그 중에서 저는 제가 가장 좋아하고 잘 할 수 있는 Swift 구현부를 중점으로 업무를 진행했습니다.
<task 1. 크고 작은 버그들을 수정하며 코드 이해하기!>
처음으로 주어진 업무는 리디북스의 앱을 전체적으로 분석하고 도식화 해보는 것이었고, 그 후에는 한 동안 버그를 수정하는데 집중했습니다. 사용자가 책장을 임의로 정렬하거나 책을 다운로드하면 책장의 정렬이 흐트러지는 버그부터 다음 책으로 넘어가면 타이머가 연동이 되지 않는 버그 등 다양한 버그 수정을 통해 앱 전반의 코드를 이해할 수 있었습니다.
<task 2. 독서노트 API 변경 및 레거시 코드 개선>
그 후에는 앱에서 비중이 있거나 앱을 사용하면 직접적으로 티가 나는 부분을 본격적으로 작업하기 시작했는데요. 내 서재와 구매 목록 탭을 통합하는 작업을 비롯해, 백엔트 팀에서 새롭게 설계한 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을 올리고 버전에 따라 실행해줘야 하는 마이그레이션 코드를 작성하면 됩니다.
이렇게해서 개발이 완료된 feature는 리디 리포지토리에 Pull Request로 등록합니다. 그러면 팀의 리드 개발자가 리뷰를 진행하게 되고, 리뷰가 완료되면 개발 브랜치에 merge가 됩니다. 그 후, 정기 배포 때 개발 브랜치는 master 브랜치에 merge되어 실제 서비스에 반영됩니다.
이번 독서 노트 동기화 작업은 겉으로는 크게 드러나지 않지만, 기존과 같은 결과물을 나타내면서도 내부적으로 많은 개선이 있었다는데 만족했습니다.
마무리
리디를 비롯해 연구실, 스타트업, 동아리 등에서 앱 개발자로 근무해보게 되었고, 작다면 작은 경험들이었지만 이 경험들을 하며 개발자는 ‘적합한 대우와 지속적인 성장 가능성이 공존하는 환경’에 있는 것이 중요하단걸 느끼게 되었습니다. 그리고 리디는 개발자에게 적합한 연봉, 복지 등을 제공해주고 성장하고 싶은 방향을 고려해 테스크를 배정해주어 이 환경을 꽤나 잘 갖춘 기업이라는 생각을 하게 되었습니다.
온보딩 기간동안 생각보다 많은 것을 느끼고 알게 되었지만, 사실 저는 이제 막 걸음마를 뗀 개발자로 앞으로의 리디 생활이 더욱 기대됩니다. 읽어주셔서 감사합니다!
고객과 발맞춰 새로운 콘텐츠 경험을 선보이는
리디와 함께할 당신을 기다립니다.