Architecture propre dans le contexte du développement multiplateforme

Bonjour à tous. Récemment, plusieurs articles ont été écrits sur le thème de l'architecture propre. Autrement dit, une architecture propre qui vous permet d'écrire des applications qui sont pratiques à maintenir et à tester. Vous pouvez lire sur l'architecture pure elle-même dans des articles aussi merveilleux que: Idées fausses sur l'architecture propre ou l' architecture propre , donc je ne vois aucune raison de répéter ce qui a déjà été écrit.

Pour commencer, permettez-moi de me présenter, je m'appelle Kakushev Rasul. Il se trouve que je me suis engagé simultanément dans le développement natif sur iOS et Android, ainsi que dans le développement de code backend pour les applications mobiles chez Navibit. Il s'agit encore d'une entreprise peu connue, qui s'apprête à entrer sur le marché de la vente de matériaux de construction. Nous avons une très petite équipe, et donc le développement d'applications mobiles tombe entièrement sur mes épaules (pas encore trop professionnelles).

Dans mon travail, je dois souvent créer une application sur iOS et Android, et comme vous le comprenez, en raison des différences de plates-formes, je dois souvent écrire plusieurs fois la même fonctionnalité. Cela prend beaucoup de temps, et il y a quelque temps, lorsque j'ai rencontré une architecture propre, j'ai pensé: les langages kotlin et swift sont assez similaires, mais les plates-formes sont différentes, mais dans l'architecture propre, il y a une couche de domaine qui n'est pas liée à la plate-forme , mais contient une logique métier pure. Que se passera-t-il si vous prenez simplement la couche de domaine entière d'Android et la transférez vers iOS, avec un minimum de changements?

Eh bien, conçu - fait. J'ai commencé le transfert. Et en effet, l'idée s'est avérée être principalement vraie. Jugez par vous-même. Par exemple, voici un interacteur sur kotlin et 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) } } 

Ou voici un exemple de l'apparence des interfaces de référentiel sur différentes plates-formes:

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? } 

La situation est similaire avec la couche de présentation, car les présentateurs et les interfaces de vue sur les deux plates-formes sont identiques. Par conséquent, grâce à un tel transfert, ma vitesse de développement a presque doublé, car en raison du fait que les couches de domaine et de présentation sont déjà entièrement formées sur les deux plates-formes, il reste une petite tâche de connecter des bibliothèques spécifiques et de modifier l'interface utilisateur et les couches de données.

Merci d'avoir lu jusqu'au bout. J'espère que cet article bénéficiera aux développeurs mobiles qui sont engagés dans le développement natif. Mes meilleurs vœux.

Source: https://habr.com/ru/post/fr419173/


All Articles