Architecture: Domain Layer— MAD Skills[번역]

DwEnn
11 min readMay 9, 2022

--

📝 NOTE : 이 글은 영상의 스크립트를 번역한 것이며 오역이 있을 수 있습니다. 😅

안녕하세요. 이 에피소드에서는 Data Layer 와 UI Layer 사이에 있는 optional Layer 인 Domain Layer 에 대해 이야기 하겠습니다.

App Architecture

여러분이 어떤 생각을 하는지 알고 있습니다. 😎
App Architecture의 다른 계층이 필요한 이유가 무었일까요 ?
App Architecture의 다른 계층에 대해 어떻게 Domain Layer 는 실제로 Architecture 를 단순화할 수 있습니다. 더 쉽게 이해할 수 있을 뿐만 아니라, 더 확장 가능하고 테스트도 간편합니다. 그러니 곧장 들어가 봅시다.

Domain Layer 는 비즈니스 로직을 가지고 있습니다. 여러분의 앱을 가치있게 만드는 규칙들과 행위들의 집합입니다. 이 비즈니스 로직은 UI 로직과는 다릅니다. UI 로직은 화면에 어떤 것을 표시하는 방법을 정의합니다. 반면에 비즈니스 로직은 이벤트와 데이터 변경이 있을 때, 무엇을 할 것인지를 정의합니다. 비즈니스 로직의 예로는 뉴스 앱에서 최신 뉴스 기사를 가져오거나 인터넷 연결이 없을 경우에 오류 메시지를 표시하는 것을 포함합니다. 작은 앱에서는 이러한 것들을 Data Layer 에서 정의하곤 합니다. 하지만 앱이 커지면서 모든 로직을 새로운 계층(Domain Layer)에 통합하는 것이 더 적합할 수 있습니다.
Domain Layer 는 데이터가 표시되는 방식에 대한 책임을 지지 않는 다는 것에 유념해야 합니다. 그것은 UI Layer 의 일입니다. 또한 데이터를 저장하거나 검색하지 않습니다. 그것은 Data Layer 의 역할이기 때문이죠.
요약하자면, Domain Layer 는 오직 비즈니스 로직만 가지고 있습니다.

Domain Layer 는 UseCases 또는 Interactors 로 알려진 클래스로 구성됩니다. 그리고 UseCase 는 사용자가 수행할 수 있거나, 앱이 사용자를 대신하여 수행하는 단일 작업을 표현합니다.

UseCase naming

이번 에피소드에서는 UseCases 클래스 이름을 지정하기 위해 다음의 규칙을 사용할 예정입니다.

  • 동사로 여러분이 하고 있는 일을 나타냅니다.(Get)
  • 여러분이 사용하려는 객체을 묘사합니다.(LatestNews)
  • suffix(UseCase)

모든 클래스에서 UseCase 와 같은 suffix 를 사용하는 것은 과해 보일지도 모르지만, 특히 큰 코드 베이스에서 코드를 훨씬 더 읽기 쉽게 만들 수 있습니다. 이 naming 규칙은 UseCase 를 명명하는 한 가지 방법일 뿐이며, 자신에게 맞는 것은 무엇이든 자유롭게 사용할 수 있습니다.

UseCase guidelines

UseCase 는 단순하고, 가볍고, 불변해야 합니다. 데이터를 캐싱해야 하는 경우, 이 로직은 Data Layer 에 더 잘 맞을 수 있습니다.

UseCase dependencies

UseCase 는 Data Layer 의 Repository 와 같은 하위 계층 또는 다른 UseCase에 의존할 수 있습니다. 그러나 ViewModel 과 같은 상위 계층에 의존해서는 안됩니다. Repository 가 하는 것처럼 데이터와 작업을 UI Layer 에게 노출해야 합니다. 코틀린을 사용하는 경우는 Suspend functions 또는 Flows 를 사용할 수 있을 것이고, 아니라면 Callback 을 사용할 수 있을 것입니다.

Examples

UseCase의 몇 가지 예를 살펴보겠습니다.
뉴스 앱에서 우리는 기사의 저자에 대한 정보와 함께 기사를 표시하기를 원하게 될 수 도 있습니다. 이를 위해서 NewRepository 에서 최신 뉴스 기사를 가져와, AuthorsRepository 의 데이터와 결합하는 아래와 같은 UseCase 를 만들 수 있습니다.

GetNewsWithAuthrosUseCase(newsRepository, authrosRepository)

또한 사용자의 local format 과 time zone 으로 기사의 게시 날짜를 표시하려고 합니다. 이것은 FormatDateUseCase 라는 첫 번째 UseCase 로 사용될 수 있습니다.

GetNewsWithAuthrosUseCase(newsRepository, authrosRepository, formatDateUseCase)

여기서 중요한 부분은, UseCase 가 재사용 가능한 로직을 포함한다는 것입니다. 그렇기에 다른 UseCase 에서도 사용이 가능합니다. Domain Layer 에서 여러 레벨의 UseCase 가 있는 것은 너무나 정상적인 일입니다.

Making UseCases callable

UseCase 는 오직 한 가지 일을 하기 때문에, Kotlin 의 invoke() 연산자를 이용하여 정상 함수처럼 호출 사능한 UseCase 객체를 만들 수 있습니다. 예를 들어, ViewModel 을 구성할 때 UseCase 를 종속성으로 전달할 때 일반적인 함수처럼 UseCase 를 호출할 수 있습니다. 이것은 UseCase 의 호출을 간결하게 하여 추가적으로 불필요한 함수가 필요하지 않게 합니다.
UseCase suffix 를 사용하는 것의 이점 중 하나는 여기서 볼 수 있습니다. Domain Layer 클래스를 호출하는지 일반 함수를 호출하는지를 명확하게 해줍니다.

val today = Calendar.getInstance()
val todaysDate = formatDateUseCase(today)

Lifecycles

  1. Scoped to caller
  2. Create a new instance when passing as a dependency

UseCase 에는 자체 Lifecycle이 없습니다. 대신, UseCase 를 사용하는 계층으로 범위가 지정됩니다. UseCase 는 변경가능한 데이터를 포함하지 않기 때문에, 종속성을 전달할 때마다 UseCase 새 인스턴스를 안전하게 생성할 수 있습니다.

Threading

  1. Must be main-safe
  2. Move job to data layer if result can be cached

이제, Threading 에 대해 이야기해 보겠습니다. UseCase 는 Main Thread 또는 UI Thread 에서 안전하게 호출되어야 합니다. UseCase 가 장기간 실행되는 blocking 작업을 수행하는 경우 Background Thread 로 이동해야합니다. 하지만 Data Layer 에서 blocking 작업을 더 잘 처리할 수 있는지를 고려해야 합니다. (Data Layer에서 처리한다면 결과를 캐시처리 할 수 있겠죠)

Common tasks

Encapsulate reusable business logic

Domain Layer 에서 수행하는 일반적인 작업들에 대해 자세히 살펴보겠습니다. 첫번째로, 재사용 가능한 비즈니스 로직의 캡슐화입니다. 여러 ViewModel 에 의해 사용되는 로직이 있다면 이 로직을 UseCase 내에 배치하세요. 예를 들어, 뉴스 앱은 사용자와 timezone 설정에 따라 날짜를 포맷하는 몇 가지 일반적인 로직을 가지고 있습니다. 전통적으로 이러한 종류의 로직은 종종 Util 클래스의 static 메소드에서 찾아볼 수 있었습니다. 이 메소드들은 대개 찾기가 어렵고 명확한 목적 없이 여러 종류의 다양한 기능을 위한 공간이 될 수 있습니다.

이러한 유형의 로직을 UseCase 로 이동하세요. App Architecture 에서는 로직의 역할을 명확히 합니다. 또한 UseCase 는 Base 클래스들의 에러 처리와 스레딩같은 공통 기능들을 공유할 수 있습니다.

Combine data from multiple repositories

UseCase 가 일반적으로 처리하는 두 번째는 여러 repository 의 데이터를 결합하는 것입니다. 뉴스 앱의 예를 더 확장시켜 보겠습니다. 우리는 각각 뉴스와 저자 데이터 작업을 처리하는 두 개의 NewsRepository, AuthrosRepository 를 가지고 있습니다. 여기서, 각 뉴스 기사 옆에 저자에 대한 더 많은 정보를 표시하고 싶습니다. 하지만 NewsRepository 는 오직 저자의 id 만 노출하고 있는 상태입니다. 저자의 정보는 AuthrosRepository 에서 얻을 수 있습니다.

우리는 이 로직을 ViewModel 에 직접 배치할 수 있습니다. 하지만 Repository 들을 결합하는 것은 복잡한 로직을 갖게 될 수도 있습니다. ViewModel 내부에 이러한 로직을 갖는 것은 불필요하게 크고, 잠재적으로 테스트하기 더 어렵게 만들 수 있습니다.

더 나은 해결책은 데이터를 결합하기 위한 UseCase 를 만드는 것입니다. 이것은 ViewModel 이 로직들로 인해 부풀어지는 것을 방지하고, 재사용가능하고 쉽게 테스트 가능한 단위 함수를 만들어줍니다.

구현부를 살펴보겠습니다. UseCase 는 Repository 들과 백그라운드 작업으로 사용할 Dispatchers 를 종속성으로 가지고 있습니다. 예제에서는 invoke() 함수를 사용하여 UseCase 를 호출할 수 있도록 합니다. 하지만 여러분의 상황에 맞는 다른 평범한 함수를 사용할 수도 있습니다.
위 함수는 결합된 데이터를 포함하는 모델을 반환합니다. 함수 안에서는 NewsRepository 에서 받는 각각의 article 을 처리하고 author 정보와 결합합니다.
이 작업은 백그라운드 스레드에서 수행됩니다. Data Layer 는 main safe 하지만 얼마나 많은 article 들을 처리하게 될지는 모릅니다. 그러므로 호출하는 스레드를 차단해서는 안됩니다.

Data access restrictions?

  • Stops domain layer logic being bypassed
  • Can make unit testing ViewModels easier
  • Forces UseCases everywhere

Domain Layer 를 구현할 때 고려해야 할 또 다른 하나는, UI Layer 에서 Data Layer 에 대한 직접 액세스를 허용해야 할지 또는 모두 Domain Layer 를 통하게 할 것인지의 여부입니다. 이 제한 사항을 만드는 것의 장점은 여러분의 UI 가 Domain Layer 로직을 우회하는 것을 막는 다는 것입니다. 예를 들어, Data Layer 에 대한 접근 요청에 대한 분석 로깅을 수행하는 경우에 Repository 가 아닌 UseCase 에만 의존하기 때문에 ViewModel 의 유닛 테스트를 더 쉽게 할 수 있습니다.
그러나 잠재적으로 매우 큰 단점은 Data Layer 에 대한 단순 함수 호출일 때에도 UseCase 사용을 강제한다는 점입니다. 이것은 복잡성을 늘려 작은 이득을 얻는 것이죠.
대부분 필요할 때만 UseCase 를 추가하는 것이 타당합니다. 필요에 따라 UI Layer 가 Data Layer 에 액세스 할 수 있도록 허용합니다. 이것은 항상 그렇듯이, 여러분 개인의 코드 베이스와 엄격한 규칙을 좋아하느냐 유연한 접근을 좋아하느냐에 달려있습니다.

Summary

  1. Reduce complexity of UI Layer
  2. Avoid duplication
  3. Improve testability

요약하자면, Domain Layer 는 비즈니스 로직을 캡슐화하여 UI Layer 의 복잡성을 줄이는데 사용될 수 있습니다. 또한 다중 ViewModel 에 의해 사용되는 로직을 단일 UseCase 로 추출하여 중복을 방지하는 데 사용될 수 있습니다. 마지막으로, 테스트를 용이하게 합니다. 로직은 한 가지 일만 하는 작은 클래스에 포함될 때 훨씬 테스트하기 쉽습니다.

Domain Layer 는 여기까지 입니다. 이제 Domain Layer 가 무엇이고 어떻게 사용하게 되는지 알게 되었습니다. 여러분의 코드 베이스에 적합한지 결정할 수 있을 것입니다.

--

--