Saubere Architektur im Kontext der plattformübergreifenden Entwicklung

Hallo an alle. In letzter Zeit wurden einige Artikel zum Thema saubere Architektur verfasst. Das heißt, eine saubere Architektur, mit der Sie Anwendungen schreiben können, die bequem zu warten und zu testen sind. Sie können über reine Architektur selbst in so wunderbaren Artikeln lesen wie: Missverständnisse von sauberer Architektur oder sauberer Architektur , daher sehe ich keinen Grund, das bereits Geschriebene zu wiederholen.

Lassen Sie mich zunächst vorstellen, mein Name ist Kakushev Rasul. Zufällig beschäftigte ich mich gleichzeitig mit der nativen Entwicklung von iOS und Android sowie mit der Entwicklung von Backend-Code für mobile Anwendungen bei Navibit. Dies ist noch ein wenig bekanntes Unternehmen, das sich gerade auf den Markteintritt für den Verkauf von Baustoffen vorbereitet. Wir haben ein sehr kleines Team, und daher liegt die Entwicklung mobiler Anwendungen ganz auf meinen (noch nicht zu professionellen) Schultern.

In meiner Arbeit muss ich oft eine Anwendung auf iOS und Android erstellen, und wie Sie verstehen, muss ich aufgrund unterschiedlicher Plattformen oft dieselbe Funktionalität mehrmals schreiben. Es dauert sehr lange, und als ich vor einiger Zeit auf eine saubere Architektur stieß, kam mir der folgende Gedanke: Die Sprachen Kotlin und Swift sind ziemlich ähnlich, aber die Plattformen sind unterschiedlich, aber in einer sauberen Architektur gibt es eine Domänenschicht, die nicht an die Plattform gebunden ist , enthält aber reine Geschäftslogik. Was passiert, wenn Sie nur die gesamte Domain-Ebene von Android übernehmen und mit minimalen Änderungen auf ios übertragen?

Gut konzipiert - fertig. Ich habe die Übertragung gestartet. Und tatsächlich stellte sich heraus, dass die Idee größtenteils wahr war. Überzeugen Sie sich selbst. Zum Beispiel ist hier ein Interaktor auf Kotlin und 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) } } 

Oder hier ein Beispiel dafür, wie Repository-Schnittstellen auf verschiedenen Plattformen aussehen:

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

Ähnlich verhält es sich mit der Präsentationsschicht, da die Präsentatoren und Ansichtsoberflächen auf beiden Plattformen gleich sind. Dank einer solchen Übertragung hat sich meine Entwicklungsgeschwindigkeit fast verdoppelt, da es aufgrund der Tatsache, dass die Domänen- und Präsentationsschichten auf beiden Plattformen bereits vollständig gebildet sind, eine kleine Aufgabe bleibt, bestimmte Bibliotheken zu verbinden und die Benutzeroberflächen- und Datenschichten zu ändern.

Vielen Dank für das Lesen bis zum Ende. Ich hoffe, dieser Artikel kommt mobilen Entwicklern zugute, die sich mit nativer Entwicklung beschäftigen. Alles Gute.

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


All Articles