 Apple Lover Developer & Artist

영속적인 디자인에 현대의 공감을 채워넣는 공방입니다

 Apple/iOS Dev Challenges

[Challenge] 3주만에 앱스토어에 앱 출시하기 - 6부 Business 계층 개발

singularis7 2024. 2. 18. 15:44
반응형

Overview

본 글은 짧은 시간을 투자하여 앱스토어에 나만의 앱을 출시하고 싶은 독자를 대상으로 개발 가이드를 제공하고자 작성하였습니다. 5부에서는 SwiftUI를 활용하여 사용자 인터페이스를 선언해보았습니다. 6부에서는 코어데이터와 SwiftUI를 연동하여 데이터베이스 규칙에 알맞게 데이터를 관리할 수 있도록 개발해보겠습니다. 독자 여러분께서는 포스팅을 읽고 "앱 개발 별거 아니네!" 생각할 수 있도록 인도할 수 있다면 본 포스팅은 목적을 다하는 것입니다. 이전 포스팅을 읽지 않으셨다면 반드시 보고 오세요.

GoodJob!

 

[Challenge] 3주만에 앱스토어에 앱 출시하기 - 5부 사용자 인터페이스 개발

Overview 본 글은 짧은 시간을 투자하여 앱스토어에 나만의 앱을 출시하고 싶은 독자를 대상으로 개발 가이드를 제공하고자 작성하였습니다. 4부에서는 앱 아키텍처를 설계하고 데이터 모델링 기

singularis7.tistory.com

Business 계층 개발

중요합니다. 사용자의 데이터를 직접 제어하는 영역이기 때문이죠. 그러면서도 단번에 설계를 완성하기가 어려워요. 개발 초기에 앞으로 발생할 모든 문제를 예측할 수도 없는 마냥이고요. 예상되는 모든 문제에 대해 선제적으로 설계를 담는 것 또한 비효율적일 것 같아요. 그래서 필자는 과감하게 결정했어요. 최소한의 설계와 코드를 사용하여 개발하고요. 문제를 마주칠 때마다 빠르게 리팩토링하여 개선하는 방향으로요. 이전에 보았던 설계도를 다시 한번 살펴볼께요.

경험한 문제점

처음 개발을 시작할 때는 Controller 역할을 담당하는 클래스 하나에 코어데이터를 제어하는 코드와 UI 상태를 관리하는 코드를 다 담았어요. 제어하는 화면 규모가 작을 때는 별문제 없어보였는데요. 규모가 커질 수록 몸소 문제가 느껴졌어요. 

코드를 수정하기가 어려웠어요. 하나의 객체가 갖는 책임이 너무 많아서요. 예를 들어 제가 처음에 작성한 코드는요. 하나의 객체가 코어데이터에 대한 CRUD도 했고요. 코어 데이터의 변화를 감지하여 UI에게 안내하는 일도 했고요. Data Model 계층의 데이터 타입을 Presentaion 계층의 데이터 타입으로 변환도 했어요. 이런 사유로 코드가 수정되어야할 이유가 너무 많았고요. 해당 코드 모듈을 수정하면 여기에 의존하는 다른 코드 모듈이 오류를 내 뿜었어요.

코드를 검증하기도 어려웠어요. 사용자의 민감한 데이터를 다루는 만큼요. 코어 데이터의 동작은 반드시 검증되어야 한다고 생각했어요. 관계에 관한 동작과 제거 규칙이 의도된 바에 따라 동작해야 했기 때문이에요. 근데요. 코어 데이터 동작에 대한 테스트 코드를 짜는게 생각보다 까다로웠어요. 예를 들어 Xcode의 테스트 모듈과 코어 데이터 및 Business Interaction 계층이 담긴 모듈이 상이한 상황을 가정해볼께요. 전자의 모듈 상에서 사용된 코어 데이터 엔티티와 후자의 모듈 상에서 사용된 코어 데이터 엔티티를 서로 다른 모델로 인식하는 오류가 생기는 등의 오류를 경험할 수 있었어요.

이 지점에서 느껴졌어요. 코드에 문제를 개선하기 위한 설계가 필요하다는 점을요.

요구사항 도출안

필자가 사용해본 리팩터링 방식의 핵심은 분리입니다. 하나의 객체에 몰려있는 책임을 식별하고요. 분리해줘요. 여러가지 코드 모듈간의 의존성 질서를 정리해줘요. 하나씩 뜯어보면서 생각해보아요. 먼저 책임을 식별해보았습니다.

  • 코어 데이터에 대한 CRUD
  • Use Case를 구현하는데 코어데이터와 Presentation 계층간의 interaction
  • 코어 데이터 모델을 Presentation 계층용 데이터 타입으로 변환
  • 코어 데이터 레코드의 변화를 감지

개발 과정에서 고려되었으면 했던 점을 다음과 같이 뽑아보았습니다.

  • 코어 데이터 엔티티에 대한 CRUD와 이에 의존하는 로직의 병행 개발 가능성
  • 추후 다른 DBMS나 네트워크 시스템에 대한 의존성을 쉽게 추가하거나 변경할 수 있는 점

개선안

앞서 선언한 요구사항을 해결하는 코드의 시작점은 한 프로토콜이였습니다.

앞서 보여드렸던 설계도를 보시면 Business 로직과 Data Model의 의존성이 뒤집혀 있는 것을 확인할 수 있어요. 의존성을 뒤집는 수단으로써 프로토콜을 사용한 것이고요. 코드를 살펴보면서 어떤 부분이 어떻게 개선되었는지 몸소 느껴보기로 해요.

Business - Core Data Model 영역 개발

먼저 코어 데이터 모델의 엔티티에 대한 CRUD가 구현되지 않아도 이 로직에 의존하는 Interaction 계층을 병행 개발할 수 있어요. stub 를 만들어서 테스트 코드도 작성 가능해졌고요.

위 코드는 모델 CRUD 연산에 대해 프로토콜 추상화에 의존하여 Interaction 계층으로 확장한 코드의 예시입니다. 프로토콜에 제네릭과 연관 타입을 사용하여 코드에 유연한 변주를 줄 수 있도록 개발되었어요. 하위 모듈이 고장나거나 수정되어도 상위 모듈로 오류 전파가 되지 않습니다.

import 부분을 확인해보면 Core Data가 포함되어있지 않음을 확인할 수 있어요. 코어 데이터에 직접 의존하는 코드들은 Repository 추상화 뒤에 숨어있어요. 상위 레이어에서는 코어 데이터에 대한 연산이 정의되지 않아도 Business 영역을 포함하여 Presentaion 영역까지 상위 레이어의 코드를 병행 개발할 수 있어요. 심지어 추상화에 의존하기 때문에 코어 데이터가 아닌 Realm과 같은 DB로 변경할 때 Repository 모듈만 변경해주면 끝나죠!

이번엔 JobPostingRepository의 사례를 살펴볼께요. 프로토콜에 정의된 CRUD 연산을 Core Data 를 활용해 구현해본 것이에요. Persistence Controller로 부터 코어데이터의 NSManagedObjectContext를 받아와서 엔티티에 대한 CRUD 연산을 구현했어요.

생성자의 경우 Persistence Controller를 주입하도록 만들었는데, 테스트 용이성을 위해서에요. Persistence Controller의 경우 테스트 시에는 실제 디스크에 저장하지 않고 인메모리 저장 방식을 사용하여 최적화하고 있기 때문입니다.

코어 데이터를 활용하여 CRUD 연산을 구현하는 흐름은 별거 없어요. 코어데이터 엔티티 모델인 NSManagedObject로 부터 FetchRequest 따와서 predicate와 sort description을 정의하여 데이터를 특정하거나 정렬할 수 있고요. 요청을 기반으로 NSManagedObjectContext가 건넨 결과 객체들을 확인하여 수정하거나 제거할 수도 있어요. 새로운 NSManagedObject를 생성한 후 NSManagedObject에 저장해주는 방식으로 새로운 객체도 생성할 수 있고요. 이런 방식으로 Repository 내부의 CRUD 연산이 조직되었습니다.

위와 같은 방식으로 Repository 내부에서는 코어 데이터 모델 엔티티 레벨의 CRUD를 제어하고 JobPostingController 등의 Interaction 영역에서는 Business 모델 엔티티 레벨의 CRUD를 제어하게 되었답니다.

Presentation - Business 영역 개발

이번에는 Business 영역과 Presentation 영역 간의 개발을 보여드릴께요. 이 지점에서는 MVVM 구조를 사용하고 있습니다. Interaction 계층을 통해 데이터를 관리하고요. ViewModel 객체가 ObservableObject 프로토콜을 채택하여 UI 상태 관리를 해줍니다. 

위 코드의 기본 프로퍼티들과 생성자를 보면 Interaction 과의 의존성을 살펴볼 수 있어요. JobPostingController와 같은 Interaction 계층을 통해 Repository를 간접적으로 제어하여 데이터를 관리합니다. ViewModel에 선언된 메서드들은 사용자 인터페이스에서 사용자의 의도가 담긴 이벤트에 의해 호출되는 됩니다. 사용자 의도에 맞추어 JobPostingController를 통해 데이터에 대한 CRUD를 구현합니다.

사용자 인터페이스의 상태 관리 관점에서 ViewModel을 보겠습니다. 데이터의 상태를 언제나 최신 상태로 동기화 시키기 위해선 데이터의 변화를 감지하는 기능이 중요하다고 생각합니다. 코어 데이터의 변화를 감지할 수 있도록 다음과 같은 데이터 관찰용 프로토콜을 선언해주었습니다.

delegate 패턴을 참고해보았습니다. 목적은 데이터가 변동되면 위임 객체에게 데이터 변동성에 관한 메시지를 전달하는 것입니다. 관찰 위임 프로토콜을 채택하여 다음과 같은 객체를 구현했습니다.

코어 데이터 프레임워크에는 UIKit 테이블 뷰 컨트롤러와 연계해서 사용할 수 있도록 데이터 감시 기능을 가진 객체가 있습니다. NSFetchedResultController가 대표적인 예시입니다. 이 객체를 통해 코어 데이터 내부의 데이터 변경 관련 이벤트를 잡아서 delegate 프로토콜을 활용하여 위임 객체 (ViewModel)에 데이터 변경 메시지를 전달해볼 것입니다.

코드 하단에 보면 DataObserverDelegate 프로토콜 채택하여 dataWillChange 메시지에 대한 행위를 선언하고 있습니다. ViewModel의 생성자 부분을 통해 delegate 관계를 등록해주었음을 확인할 수 있죠.

ViewModel이 objectWillChange 라는 Combine 이벤트를 들고 있는 이유는 ObservableObject 프로토콜을 채택하고 있기 때문입니다. 그동안 @Published 등의 Property-Wrapper를 통해 감춰진 코드를 명시적으로 호출해주는 코드일 뿐입니다. SwiftUI 프레임워크는 objectWillChange 이벤트를 받으면 UI를 갱신합니다. ViewModel 또한 DataObserver가 제공하는 dataWillChange 메시지에 맞추어 UI 상태를 갱신하고 있네요.

마무리

여기까지 오시느라 수고많으셨습니다. 이렇게 Business 영역의 핵심 아이디어에 대한 소개를 마무리하겠습니다. 여기서 보여드린 코드 또한 개발 진행중인 코드이기 때문에 앞으로 마주하는 개발 문제에 맞추어 언제든지 개선될 여지가 있습니다. 다만, 초기에 계획했던 MVP를 만드는데 필요한 규모의 설계 정도로 귀엽게 봐주시면 좋을 것 같아요.

다음번 포스팅을 통해 배포에 관하여 제가 사용한 방식을 소개해드리며 이번 연재를 마무리 지어볼까 합니다. 감사합니다.

반응형