Vous avez l’impression que vous devez jeter la moitié de votre code tous les 6 mois ? Plus le temps passe, moins votre application mobile est maintenable ?
Injection de dépendances, routage ou gestion des environnements, venez découvrir nos conseils pour vous aider à réaliser des applications mobiles évolutives conçues pour durer dans le temps.
Par Jean-Christophe Pastant, Mobile Engineer chez Xebia
Toutes les informations sur xebicon.fr
9. @Xebiconfr #Xebicon18 @pjechris
Architecturer ?
Une bonne architecture pérenne se doit d’être :
● Simple
KEEP IT SIMPLE, STUPID
● Pragmatique
YOU AIN’T GONNA NEED IT
● Fonctionnelle/métier
DOMAIN DRIVEN DESIGN
● Modifiable/évolutive
SEPARATION OF CONCERN, OPEN/CLOSED PRINCIPLE
9
11. @Xebiconfr #Xebicon18 @pjechris
Structurer en couches permet de :
● Séparer les responsabilités
○ Affichage
○ Appels réseaux
○ ….
● Réfléchir quel rôle (couche) joue une classe
● Être plus ouvert aux changements
○ On est agnostique des couches supérieures
Structurer en couches
11
15. @Xebiconfr #Xebicon18 @pjechris
Exemple
● Pouvoir ajouter des livres dans un panier
● Afficher le panier
● Afficher le prix total du panier
source : application Fnac (Android)
15
20. @Xebiconfr #Xebicon18 @pjechris
Modèles métier
● Notion centrale du métier/fonctionnel dans l’application
● Donne du sens dans la communication entre les couches
● Doit être orienté métier
● Objets simples
○ Immutables
○ Pas de réseau, pas d’asynchrone, etc...
20
31. @Xebiconfr #Xebicon18 @pjechris
UI
class CartItemView: UIView {
var priceLabel: UILabel!
var title: UILabel!
func configure(book: Book) {
priceLabel.text = stringify(book.price, .currency)
title.text = book.title.uppercased
}
}
class CartRecapView: UIView {
var priceLabel: UILabel!
func configure(cart: Cart) {
priceLabel.text = stringify(cart.total, .currency)
}
}
31
32. @Xebiconfr #Xebicon18 @pjechris
UI
class CartItemView: UIView {
var priceLabel: UILabel!
var title: UILabel!
func configure(book: Book) {
priceLabel.bind(book.price, with: PriceComponent.self)
title.bind(book.title, with: TitleComponent.self)
}
}
class CartRecapView: UIView {
var priceLabel: UILabel!
func configure(cart: Cart) {
priceLabel.bind(cart.total, with: PriceComponent.self)
}
}
32
33. @Xebiconfr #Xebicon18 @pjechris
UI
● Faire des petits composants UI réutilisables
● Préférer combiner les composants plutôt que l’héritage
● Exposer une API métier
33
36. @Xebiconfr #Xebicon18 @pjechris
Métier
class CartComponentContext {
// API
private(set) var cart: Variable<Cart>
let checkoutAction: Action
private let httpRepository: XebiaWSRepository
init(cart: Cart) {
self.cart = cart
self.httpRepository = XebiaWSRepository(...)
}
}
36
37. @Xebiconfr #Xebicon18 @pjechris
Métier
class CartComponentContext {
// API
private(set) var cart: Variable<Cart>
let checkoutAction: Action
private let httpRepository: XebiaWSRepository
init(cart: Cart) {
self.cart = cart
self.httpRepository = XebiaWSRepository(...)
self.checkoutAction = Action(
enabledIf: { !cart.isEmpty },
factory: { httpRepository.checkout(self.cart) })
}
}
37
38. @Xebiconfr #Xebicon18 @pjechris
Métier
● Lien entre la couche données et la couche UI
● Définissent les actions / règles métier
○ Passer une commande seulement si le panier n’est pas vide
○ …
● Font les appels réseaux
● Gérent les états
● Exposent une API métier
38
46. @Xebiconfr #Xebicon18 @pjechris
Données
● Doit être simple mais fiable et lisible
● Stateless
● Mapping 1-to-1 avec les services fournies
○ 1 classe = 1 service (DB, WS, …)
○ 1 méthode = 1 API
46
49. @Xebiconfr #Xebicon18 @pjechris
Providers
class CartProvider {
let httpRepository: XebiaWSRepository
let realmRepository: RealmRepository
func checkout(cart: Cart) -> Promise<Void> {
return httpRepository
.checkout(cart)
.then { realmRepository.savePurchasedCart($0) }
}
}
49
50. @Xebiconfr #Xebicon18 @pjechris
Providers
class CartComponentContext {
// API
private(set) var cart: Variable<Cart>
let checkoutAction: Action
private let httpRepository: XebiaWSRepository
init(cart: Cart) {
self.cart = cart
self.httpRepository = XebiaWSRepository(...)
self.checkoutAction = Action(
enabledIf: { !cart.isEmpty },
factory: { httpRepository.checkout(self.cart) })
}
}
50
51. @Xebiconfr #Xebicon18 @pjechris
Providers
class CartComponentContext {
// API
private(set) var cart: Variable<Cart>
let checkoutAction: Action
private let cartProvider: CartProvider
init(cart: Cart) {
self.cart = cart
self.cartProvider = CartProvider(...)
self.checkoutAction = Action(
enabledIf: { !cart.isEmpty },
factory: { cartProvider.checkout(cart: self.cart) })
}
}
51
52. @Xebiconfr #Xebicon18 @pjechris
Providers
● Facilite la synchronisation/les accès à plusieurs Repository
● Réduit la logique dans les Smart Components
● 1 classe par objet métier (BookProvider, CartProvider, …)
52
54. @Xebiconfr #Xebicon18 @pjechris
Injection de dépendances
class CartComponentContext {
// API
private(set) var cart: Variable<Cart>
let checkoutAction: Action
private let cartProvider: CartProvider
init(cart: Cart) {
self.cart = cart
self.cartProvider = CartProvider(...)
self.checkoutAction = Action(
enabledIf: { !cart.isEmpty },
factory: { cartProvider.checkout(cart: self.cart) })
}
}
54
55. @Xebiconfr #Xebicon18 @pjechris
Injection de dépendances
class CartComponentContext {
// API
private(set) var cart: Variable<Cart>
let checkoutAction: Action
private let cartProvider: CartProvider
init(cart: Cart, cartProvider: CartProvider) {
self.cart = cart
self.cartProvider = cartProvider
self.checkoutAction = Action(
enabledIf: { !cart.isEmpty },
factory: { cartProvider.checkout(cart: self.cart) })
}
}
55
56. @Xebiconfr #Xebicon18 @pjechris
Injection de dépendances
● Aide à garder les couches indépendantes
● Limite la connaissance de chaque objet au strict minimum
● Permet de tester plus facilement
56
58. @Xebiconfr #Xebicon18 @pjechris
Tests unitaires
Tester doit être simple
Si :
● Vous écrivez trop de lignes pour un test (+ 10 lignes)
● C’est trop compliqué de tester malgré tous vos protocoles/interfaces
Revoyez votre architecture !
58