CareVision
환자 곁에서 늘 함께, 간호사의 눈이 되어주는 환자 모니터링 서비스
Github : https://github.com/SSU-Capstone-Aurora
3-2 소프트웨어학부 캡스톤1 수업에서 진행하는 프로젝트 입니다.
이전 포스팅에서 CareVision은 다음과 같은 구조로 개발을 진행할 예정이라고 설명했습니다!
그림에서 Data Layer를 보시면 Repository가 data source를 알고 있고, 이 데이터 계층을 도메인 레이어가 알고 있는 것을 알 수 있는데요,
각 Domain 계층과 Data계층이 독립적인 역할을 수행하여 코드의 응집력을 높이고 계층간의 결합도를 낮추는 것이 목표입니다.
도메인 계층에서는 비즈니스 로직을 담습니다. 아직 비즈니스 로직이 정확히 무엇인지 헷갈릴 때가 많은데요,
CareVision이라는 서비스에서의 비즈니스 로직으로 예를 들면, 알림을 통해 간호사가 환자의 상태를 실시간으로 파악하는 것이나, 특정 조건을 만족하는 환자들에게만 알림을 보내는 등의 기능이 비즈니스 로직에 포함될 것입니다.
또한 데이터 계층에 대한 의존성을 가지고, 필요한 데이터를 요청하거나 가공해서 제공하는 역할을 담당합니다.
이와 같은 구조를 가지고 있기에 Repository는 Data Source에서 데이터를 가져와 도메인에 전달하는 역할을 수행합니다. Repository가 dataSource의 진입점으로 사용하는 것입니다.
참고자료 - 성빈랜드
따라서 Domain Layer는 데이터의 출처를 신결쓸 필요 없이 Data 계층에게 필요한 데이터만 요청하고, 필요한 데이터만 받을 수 있습니다.
데이터 계층은 앱 내에서 사용하는 데이터의 출처를 관리합니다.
DateSource는 데이터 계층에 위치하며, 실제 데이터의 저장 위치나 출처를 나타냅니다.
DataSource는 주로 RemoteDataSource와 LocalDataSource로 나누어지며, 각각의 역할이 명확하게 구분됩니다.
위의 사진에서 볼 수 있듯이 각 Datasource의 역할은 다음과 같습니다.
Remote DataSource: 원격 서버와의 통신을 통해 데이터를 가져오는 역할
Local DataSource: 로컬 데이터베이스나 캐싱 메커니즘을 통해 데이터를 저장하고 관리하는 역할
그러면 CareVision에서 API 통신을 위해 사용한 DataSource와 Repository 코드를 함께 살펴보겠습니다.
CareVision에서는 Retrofit을 통해 네트워크 요청을 수행하고 있는데요, 그렇기 때문에 API를 요청하기 위해서는 interface로 구현된 서비스 파일이 필요합니다.
평소에 이와 같은 생각을 했었는데요,
Retrofit은 REST API와 통신하는 네트워크 요청을 간단하고 효율적으로 처리하기 위해 개발된 라이브러리입니다. 따라서 인터페이스에 정의된 메서드를 바탕으로 런타임 시점에 자동으로 구현체를 생성주기 때문에 인터페이스에 네트워크 요청의 엔드포인트와 HTTP 메서드를 정의하면, 이를 바탕으로 런타임에 네트워크 요청을 처리하는 구현체를 자동 생성한다고 합니다.
interface NurseAuthService {
// 닉네임 중복 확인 api
@GET("api/check-username")
suspend fun checkDuplication(@Query ("username") username: String): BaseResponse<Boolean>
}
다시 돌아와 우선 패키지 구조는 다음과 같습니다.
Data패키지
datasource폴더
데이터의 출처를 추상화하여 관리하는 데이터 소스가 위치합니다.
NurseAuthRemoteDataSource
인터페이스와 이를 구현한 DefaultNurseAuthDataSource
가 포함됩니다.
NurseAuthRemoteDataSource 클래스는 원격 서버와의 통신을 담당합니다.
interface NurseAuthRemoteDataSource {
suspend fun checkUsername(username: String): Boolean
}
DefaultNurseAuthDataSource는 이에 대한 구체적인 구현을 제공합니다.
NurseAuthService를 주입하여, 클래스 내에서 이를 사용해 네트워크 요청을 수행합니다.
class DefaultNurseAuthDataSource @Inject constructor(
private val nurseAuthService: NurseAuthService
) : NurseAuthRemoteDataSource {
override suspend fun checkUsername(username: String): Boolean {
return nurseAuthService.checkDuplication(username).result
}
}
위에서 nurseAuthService을 주입하였는데요, 현재 Dagger Hilt를 통해 의존성 주입을 사용하고 있습니다. 따라서, 아래와 같은 AuthModule 파일이 있어야 NurseAuthService를 주입하여 사용할 수 있습니다.
@Module
@InstallIn(SingletonComponent::class)
object AuthModule {
@Provides
@Singleton
// NurseAuthService 인터페이스를 구현한 객체를 제공
// @Unsecured 어노테이션이 붙은 Retrofit 객체를 사용
// provideLoginApi 사용되는 곳은 viewModel, repository 등 필요한 곳
fun provideNurseAuthApi(@Unsecured retrofit: Retrofit): NurseAuthService = retrofit.create()
}
마찬가지로 이후 코드에서 repository와 dataSource도 주입하여 사용해야 하기에 아래와 같은 di 모듈을 생성합니다
@Module
@InstallIn(SingletonComponent::class)
abstract class AuthRepositoryModule {
@Binds
@Singleton
abstract fun bindRemoteDataSource(
defaultNurseAuthDataSource: DefaultNurseAuthDataSource
): NurseAuthRemoteDataSource
@Binds
@Singleton
abstract fun bindNurseAuthRepository(
defaultNurseAuthRepository: DefaultNurseAuthRepository
): NurseAuthRepository
}
Domain패키지
다음은 도메인 패키지 내 파일들 입니다.
NurseAuthRepository이 존재하는데, 이 파일은 간호사 인증과 관련된 데이터 요청을 처리하기 위한 인터페이스 파일입니다.
interface NurseAuthRepository {
suspend fun checkUsername(username: String): Boolean
}
해당 인터페이스를 구현하여 viewmodel 또는 Usecase와 같은 상위 셰층에서 구체적인 구현에 의존하지 않고, 데이터를 사용할 수 있게 됩니다. 이에 대한 구현체인 DefaultNurseAuthRepository는 다시 Data패키지에 위치합니다.
class DefaultNurseAuthRepository @Inject constructor(
private val remoteDataSource: NurseAuthRemoteDataSource
) : NurseAuthRepository {
override suspend fun checkUsername(username: String): Boolean {
return remoteDataSource.checkUsername(username)
}
}
해당 구현체 클래스는 remoteDataSource로부터 받은 데이터를 관리하고 상위 계층에게 데이터를 제공하는 역할을 수행하고 있기에 data 패키지(모듈)에 존재하는 것이 적합합니다.
이렇게 가공된 데이터는 다음과 같이 뷰모델에 repository를 주입하여 사용할 수 있습니다.
@HiltViewModel
class NurseSignUpViewModel @Inject constructor(
private val nurseAuthRepository: NurseAuthRepository
) : ViewModel() {
...