Clean architecture в контексте кроссплатформенной разработки

Всем привет. В последнее время довольно много статей написано на тему clean architecture. То есть чистой архитектуры, которая позволяет писать приложения, удобные в сопровождении и тестировании. Про саму чистую архитектуру вы можете прочитать в таких замечательных статьях как: Заблуждения Clean Architecture или Чистая архитектура, поэтому не вижу смысла повторять то, что уже написано.

Для начала позвольте представиться, меня зовут Какушев Расул. Так уж получилось что я одновременно занимаюсь нативной разработкой на ios и android, а так же разработкой backend-кода мобильных приложений, в компании Navibit. Это пока еще малоизвестная компания, которая только готовится выйти на рынок продажи строительных материалов. У нас очень маленькая команда и поэтому разработка мобильных приложений целиком и полностью ложится на мои (еще не слишком профессиональные) плечи.

В моей работе часто приходится делать одно приложение на ios и android, и как вы понимаете, в силу различий платформ, часто приходится писать один и тот же функционал несколько раз. Это занимает довольно много времени, и поэтому некоторое время назад, когда я познакомился с clean architecture, мне пришла в голову такая мысль: языки kotlin и swift довольно похожи, однако платформы различаются, но в clean architecture есть domain слой, который не привязан к платформе, а содержит чистую бизнес-логику. Что будет если просто взять весь domain слой из android и перенести его в ios, с минимальными изменениями?

Что же, задумано — сделано. Я начал перенос. И действительно идея оказалась в большинстве своем верной. Сами посудите. К примеру вот один интерактор на kotlin и swift:

Kotlin (Android)

class AuthInteractor @Inject internal constructor(private val authRepository: AuthRepository,                      private val profileRepository: ProfileRepository) {      fun auth(login: String, password: String, cityId: Int): Single<Auth> = authRepository.auth(login.trim { it <= ' ' }, password.trim { it <= ' ' }, cityId, cloudToken)      fun restore(login: String, password: String, cityId: Int, confirmHash: String): Single<AuthInfo> = authRepository.restore(login.trim { it <= ' ' }, password.trim { it <= ' ' }, cityId, confirmHash)      fun restore(password: String, confirmHash: String): Single<AuthInfo> = authRepository.restore(password.trim { it <= ' ' }, confirmHash)      fun getToken(): String = authRepository.checkIsAuth()      fun register(login: String,                  family: String,                  name: String,                  password: String,                  cityId: Int,                  confirmHash: String): Single<AuthInfo> =             authRepository.register(login.trim { it <= ' ' },                     family.trim { it <= ' ' },                     name.trim { it <= ' ' },                     password.trim { it <= ' ' },                     cityId, confirmHash)      fun checkLoginAvailable(login: String): Single<LoginAvailable> = authRepository.checkLoginAvailable(login)      fun saveTempCityInfo(authCityInfo: AuthCityInfo?) = authRepository.saveTempCityInfo(authCityInfo)      fun checkPassword(password: String): Single<AuthInfo> = authRepository.checkPassword(password)      fun auth(auth: Auth) {         authRepository.saveToken(auth.token!!)         profileRepository.saveProfile(auth.name!!, auth.phone!!, auth.location!!)     }      companion object {         const val AUTH_ERROR = "HTTP 401 Unauthorized"     } } 

Swift (iOS):

class AuthInteractor {          public static let AUTH_ERROR = "HTTP 401 Unauthorized"          private let authRepository: AuthRepository     private let profileRepository: ProfileRepository     private let cloudMessagingRepository: CloudMessagingRepository          init(authRepository: AuthRepository,             profileRepository: ProfileRepository,             cloudMessagingRepository: CloudMessagingRepository) {         self.authRepository = authRepository         self.profileRepository = profileRepository         self.cloudMessagingRepository = cloudMessagingRepository     }          func auth(login: String, password: String, cityId: Int) -> Observable<Auth> {         return authRepository.auth(login: login.trim(), password: password.trim(), cityId: cityId, cloudMessagingToken: cloudMessagingRepository.getCloudToken())     }          func restore(login: String, password: String, cityId: Int, confirmHash: String) -> Observable<AuthInfo> {         return authRepository.restore(login: login.trim(), password: password.trim(), cityId: cityId, confirmHash: confirmHash)     }          func restore(password: String, confirmHash: String) -> Observable<AuthInfo> {         return authRepository.restore(password: password.trim(), confirmHash: confirmHash)     }          func getToken() -> String {         return authRepository.checkIsAuth()     }          func register(login: String,             family: String,             name: String,             password: String,             cityId: Int,             confirmHash: String) -> Observable<AuthInfo> {         return authRepository.register(login: login.trim(),                                     family: family.trim(),                                     name: name.trim(),                                     password: password.trim(),                                     cityId: cityId,                                     confirmHash: confirmHash)     }          func checkLoginAvailable(login: String) -> Observable<LoginAvailable> {         return authRepository.checkLoginAvailable(login: login)     }          func saveTempCityInfo(authCityInfo: AuthCityInfo?) {         authRepository.saveTempCityInfo(authCityInfo: authCityInfo)     }          func checkPassword(password: String) -> Observable<AuthInfo> {         return authRepository.checkPassword(password: password)     }          func auth(auth: Auth) {         authRepository.saveToken(token: auth.token)         profileRepository.saveProfile(name: auth.name, phone: auth.phone, location: auth.location)     } } 

Или же вот пример того как выглядят интерфейсы репозиториев на различных платформах:

Kotlin (Android)

interface AuthRepository {      fun auth(login: String, password: String, cityId: Int, cloudMessagingToken: String): Single<Auth>      fun register(login: String,                  family: String,                  name: String,                  password: String,                  cityId: Int,                  confirmHash: String): Single<AuthInfo>      fun restore(login: String, password: String, cityId: Int, confirmHash: String): Single<AuthInfo>      fun restore(password: String, confirmHash: String): Single<AuthInfo>      fun checkLoginAvailable(login: String): Single<LoginAvailable>      fun sendCode(login: String): Single<CodeCheck>      fun checkCode(hash: String, code: String): Single<CodeConfirm>      fun checkIsAuth(): String      fun saveToken(token: String)      fun removeToken()      fun notifyConfirmHashListener(confirmHash: String)      fun getResendTimer(time: Long): Observable<Long>      fun checkPassword(password: String): Single<AuthInfo>      fun saveTempCityInfo(authCityInfo: AuthCityInfo?)      fun saveTempConfirmInfo(codeConfirmInfo: CodeConfirmInfo)      fun getTempCityInfo(): AuthCityInfo?      fun getConfirmHashListener(): Observable<String>      fun getTempConfirmInfo(): CodeConfirmInfo? } 

Swift (iOS):

protocol AuthRepository {          func auth(login: String, password: String, cityId: Int, cloudMessagingToken: String) -> Observable<Auth>          func register(login: String, family: String, name: String, password: String, cityId: Int, confirmHash: String) -> Observable<AuthInfo>          func restore(login: String, password: String, cityId: Int, confirmHash: String) -> Observable<AuthInfo>          func restore(password: String, confirmHash: String) -> Observable<AuthInfo>          func checkLoginAvailable(login: String) -> Observable<LoginAvailable>          func sendCode(login: String) -> Observable<CodeCheck>          func checkCode(hash: String, code: String) -> Observable<CodeConfirm>          func checkIsAuth() ->String          func saveToken(token: String)          func removeToken()          func notifyConfirmHashListener(confirmHash: String)          func getResendTimer(time: Int) -> Observable<Int>          func checkPassword(password: String) -> Observable<AuthInfo>          func saveTempCityInfo(authCityInfo: AuthCityInfo?)          func saveTempConfirmInfo(codeConfirmInfo: CodeConfirmInfo)          func getTempCityInfo() -> AuthCityInfo?          func getConfirmHashListener() -> Observable<String>          func getTempConfirmInfo() -> CodeConfirmInfo? } 

Аналогично дело обстоит и с presentation слоем, так как презентеры и view-интерфейсы на обеих платформах одинаковы. Поэтому благодаря такому переносу, моя скорость разработки увеличилась почти вдвое, так как из-за того, что на обеих платформах уже полностью сформированы domain и presentation слои, остается дело за малым — подключить специфичные библиотеки и доработать ui и data слои.

Спасибо за то что дочитали до конца. Надеюсь данная статья принесет пользу мобильным разработчикам, которые занимаются нативной разработкой. Всего наилучшего.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *