Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Pilares do desenvolvimento Mobile no Nubank

649 views

Published on

Abordaremos os seguintes temas relacionados ao processo de desenvolvimento no Nubank: - Arquitetura: - Diferenças da arquitetura padrão da Apple para a arquitetura que usamos no Nubank - Escalabilidade e Manutenção - Como mantemos a nosso base de código - Como deixamos o aplicativo mais escalável quebrando ele em partes menores - Testing: - Testes de UI com KIF - Vaidação de layout com screenshots

Published in: Technology
  • Be the first to comment

Pilares do desenvolvimento Mobile no Nubank

  1. 1. Como desenvolver um app em grande escala? Pilares do Desenvolvimento Mobile Victor Maraccini Luiz Dubas @vmaraccini @luizdubas
  2. 2. Quem somos 2 • Emissora de cartão de crédito • 8 milhões de pedidos de cartão • 80 desenvolvedores; 14 mobile • Android, WP, iOS (+ watchOS, Widget)
  3. 3. Agenda 3 • Arquitetura • Manutenção e Escalabilidade • Integração Contínua e Testes
  4. 4. 4 Arquitetura • Arquitetura MVC Apple • Arquitetura MVVM-C
  5. 5. 5 Arquitetura MVC Apple Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela VC M
  6. 6. 6 Arquitetura MVC Apple VC M Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela
  7. 7. 7 Arquitetura MVC Apple VC M Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela • Código para fazer requisições Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela
  8. 8. 8 Arquitetura MVC Apple M VC Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela • Código para fazer requisições Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela
  9. 9. 9 Arquitetura MVC Apple M VC Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela • Código para fazer requisições • Código para navegar entre telas Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela • Código para fazer requisições Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela
  10. 10. 10 Arquitetura MVC Apple VC M Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela • Código para fazer requisições • Código para navegar entre telas Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela • Código para fazer requisições Model • Entidades mapeadas do servidor • Representação de uma tela View Controller • Código para montar a tela
  11. 11. 11 Arquitetura MVC Apple • Lógica fica concentrada em um lugar (ViewController) • Base de código difícil de manter • Código difícil de testar
  12. 12. 12 Arquitetura • Arquitetura MVC Apple • Arquitetura MVVM-C
  13. 13. 13 Arquitetura MVVM-C class MegaViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let tableView = UITableView() let deleteButton = UIButton() /* Other ViewController Properties */ override func viewDidLoad() { view.backgroundColor = .white view.addSubview(tableView) view.addSubview(deleteButton) tableView.delegate = self tableView.dataSource = self deleteButton.addTarget(self, action: #selector(removeSelectedItem), for: .touchUpInside) } /* Table view delegate and data source implementation */ func getTableData() { /* Logic to get new items from the server */ } func removeSelectedItem() { /* Logic to remove item from list and server */ } } VC
  14. 14. 14 Arquitetura MVVM-C class SimpleView: UIView { let tableView = UITableView() let deleteButton = UIButton() override init(frame: CGRect) { backgroundColor = .white addSubview(tableView) addSubview(deleteButton) } } VC V
  15. 15. 15 Arquitetura MVVM-C class MegaViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { var events: Events? var selectedPath: IndexPath? /* View Binding logic */ /* Table view delegate and data source implementation */ func getTableData() { URLSession(configuration: URLSessionConfiguration.default).rx .response(request: URLRequest.getEvents()) .map({ jsonData -> Events in self?.events = Events.from(jsonData) }) } func removeSelectedItem() { guard let events = events, index = selectedPath else { return } let event = events[index.row] URLSession(configuration: URLSessionConfiguration.default).rx .response(request: URLRequest.delete(event: event)) .map({ jsonData -> Events in self?.events = Events.from(jsonData) }) } } VC V
  16. 16. 16 Arquitetura MVVM-C class DataManager { func getEvents(completion: (Events) -> Void) { URLSession(configuration: URLSessionConfiguration.default).rx .response(request: URLRequest.getEvents()) .map({ jsonData -> Events in completion(Events.from(jsonData)) }) } func delete(event: Event, completion: (Events) -> Void) { URLSession(configuration: URLSessionConfiguration.default).rx .response(request: URLRequest.delete(event: event)) .map({ jsonData -> Events in completion(Events.from(jsonData)) }) } } VC VMgr
  17. 17. 17 Arquitetura MVVM-C class SimpleController { let manager = DataManager() let viewController: SimpleViewController func handle(actions: SimpleViewController.Actions) { switch actions { case .loadData: getTableData() case let .delete(event): delete(event: event) } } func getTableData() { manager.getEvents { [weak self] in self?.viewController.bind(SimpleViewModel(events: event)) } } func delete(event: Event) { manager.delete(event: event) { [weak self] in self?.viewController.bind(SimpleViewModel(events: event)) } } } struct SimpleViewModel { let events: [Event] let deleteLabel: String = “…” } VC VC VM Mgr M
  18. 18. 18 Arquitetura MVVM-C class SimpleViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { typealias Handler = (Actions) -> Void enum Actions { case .loadData case .delete(Event) } let actionHandler: Handler var selectedPath: IndexPath? var viewModel: SimpleViewModel? open override func loadView() { let view = SimpleView() view.bounds = CGRect(origin: .zero, size: UIScreen.main.bounds.size) self.view = view } func bind(withViewModel viewModel: SimpleViewModel) { self.viewModel = viewModel view.deleteButton.setTitle(viewModel.deleteLabel, for: .normal) } /* Table view delegate and data source implementation */ } VC VC VM Mgr M
  19. 19. Coordinator 19 Arquitetura MVVM-C VC VC VM VC VC VM
  20. 20. public class SimpleCoordinator { let context: UINavigationController var firstScreen: FirstScreenController? var secondScreen: SecondScreenController? init(context: UINavigationController) { self.context = context } public func start() throws { presentFirstScreen() } func presentFirstScreen() { let screen = FirstScreenController(completeHandler: { [weak self] in presentSecondScreen() }) context.pushViewController(screen.viewController, animated: true) firstScreen = screen } func presentSecondScreen() { /* Logic to present second screen */ } } 20 Arquitetura MVVM-C
  21. 21. 21 Arquitetura MVVM-C public class SimpleCoordinator { let context: UINavigationController var firstScreen: FirstScreenController? var secondScreen: SecondScreenController? init(context: UINavigationController) { self.context = context } public func start() throws { presentFirstScreen() } func presentFirstScreen() { let screen = FirstScreenController(completeHandler: { [weak self] in presentSecondScreen() }) context.pushViewController(screen.viewController, animated: true) firstScreen = screen } func presentSecondScreen() { /* Logic to present second screen */ } } init(context: UINavigationController) { self.context = context }
  22. 22. 22 Arquitetura MVVM-C public class SimpleCoordinator { let context: UINavigationController var firstScreen: FirstScreenController? var secondScreen: SecondScreenController? init(context: UINavigationController) { self.context = context } public func start() throws { presentFirstScreen() } func presentFirstScreen() { let screen = FirstScreenController(completeHandler: { [weak self] in presentSecondScreen() }) context.pushViewController(screen.viewController, animated: true) firstScreen = screen } func presentSecondScreen() { /* Logic to present second screen */ } } public func start() throws { presentFirstScreen() } func presentFirstScreen() { let screen = FirstScreenController(completeHandler: { [weak self] in presentSecondScreen() }) context.pushViewController(screen.viewController, animated: true) firstScreen = screen }
  23. 23. 23 Arquitetura MVVM-C public class SimpleCoordinator { let context: UINavigationController var firstScreen: FirstScreenController? var secondScreen: SecondScreenController? init(context: UINavigationController) { self.context = context } public func start() throws { presentFirstScreen() } func presentFirstScreen() { let screen = FirstScreenController(completeHandler: { [weak self] in presentSecondScreen() }) context.pushViewController(screen.viewController, animated: true) firstScreen = screen } func presentSecondScreen() { /* Logic to present second screen */ } } completeHandler: { [weak self] in presentSecondScreen() } func presentSecondScreen() { /* Logic to present second screen */ }
  24. 24. 24 Arquitetura MVVM-C • Código mais segmentado • Cada classe fica com uma única responsabilidade • Código reutilizável • Mais fácil de manter • Mais fácil de testar
  25. 25. Agenda 25 • Arquitetura • Manutenção e Escalabilidade • Integração Contínua e Testes
  26. 26. 26 Manutenção e Escalabilidade Problemas • Migrando de Objetive-C para Swift • Tempo de compilação • Code base crescendo rápido
  27. 27. 27 Manutenção e Escalabilidade Solução: Separar o app em segmentos • No Swift: Frameworks Dinâmicos • Separar e desacoplar novas features e código compartilhado ✓ Distribuímos os testes entre os frameworks ✓ Compilamos cada feature apenas 1 vez ✓ Facilita a manutenção do código legacy
  28. 28. 29 Manutenção e Escalabilidade import PaymentFlow
  29. 29. 30 Manutenção e Escalabilidade import PaymentFlow ... let coordinator = PaymentFlow.Coordinator(context: viewController) coordinator.start() ...
  30. 30. 31 Manutenção e Escalabilidade Lógica de negócios/apresentação import PaymentFlow ... let coordinator = PaymentFlow.Coordinator(context: viewController) coordinator.start() ...
  31. 31. 33 Manutenção e Escalabilidade App Novas Features Features Legacy Common Legacy Core Common Swift Lógica de negócios, API iOS, watchOS, App Extension Específico do iOS Interface de usuário Telas e fluxos completos Integração Lógica entre features
  32. 32. 34 Manutenção e Escalabilidade App Novas Features Features Legacy Common Legacy Core Common Swift https://github.com/Carthage/Carthage
  33. 33. Agenda 35 • Integração Contínua e Testes • Arquitetura • Manutenção e Escalabilidade
  34. 34. Agenda 36 • Arquitetura • Manutenção e Escalabilidade • Integração Contínua e Testes
  35. 35. Testes • Uma feature não está completa se não estiver testada • Unitários: Comportamento individual das classes • Dado um input, testar o output • Testar efeitos colaterais • UI: Funcionamento de fluxos, interações entre telas 37 Integração Contínua e Testes
  36. 36. Integração Contínua: Pipelines • Rodam todos os testes para cada PR e merge na branch principal • Garantem que nosso código funciona • Release só se a Pipeline ficar ✅ 38 Integração Contínua e Testes
  37. 37. 39 Integração Contínua e Testes Go-CD • Roda os testes de toda a empresa • Vários estágios para cada pipeline • Último estágio faz o release (Interno ou para a App Store)
  38. 38. Testes Unitários de View 41 Integração Contínua e Testes • Diferentes tamanhos (iPhone 4, 5, 6, 6 Plus) • Diferentes tipos de conteúdo/estados
  39. 39. 42 Integração Contínua e Testes Testes Unitários de View • Diferentes tamanhos (iPhone 4, 5, 6, 6 Plus) • Diferentes tipos de conteúdo/estados
  40. 40. 43 Integração Contínua e Testes let statePermutations = images .extensiveCombine(titles) .extensiveCombine(tintColor) let images = [UIImage(), UIImage()] let titles = [NURandomGenerator().attributedString(0), NURandomGenerator().attributedString(1)] let tintColors = [NURandomGenerator().color(2), NURandomGenerator().color(3)] Testes Unitários de View
  41. 41. 44 Integração Contínua e Testes func assertButton(_ button: Button, prefix: String) throws { try statePermutations.forEach { image, title, color in button.image = image button.attributedTitle = title button.tintColor = color try assertView(button, width: 150, height: 32, identifier: identifier tolerance: 0, screenshotService: service) } } let statePermutations = images .extensiveCombine(titles) .extensiveCombine(tintColor) Testes Unitários de View
  42. 42. 45 Integração Contínua e Testes Testes Unitários de View • Diferentes UIContentSizeCategory
  43. 43. 46 Integração Contínua e Testes Testes de UI • Testes usando a biblioteca KIF: Keep it Functional tester().tapScreen(at: tapPoint) tester().tapView(withAccessibilityIdentifier:) tester().waitForAnimationsToFinish() func openSettings() { tester().waitForFeedLoaded() tester().dismissFeed() tester().tapView(withAccessibilityIdentifier: "...") tester().waitForAnimationsToFinish() } Bonus: Seu app fica acessível
  44. 44. Quantos Testes? 47 Integração Contínua e Testes • UI: • 30 fluxos de teste • 58 minutos Build + Test • UI: • 30 fluxos de teste • 58 minutos Build + Test • Unitários: • 3.655 testes • 21 minutos Build + Test
  45. 45. Testes em paralelo 48 Integração Contínua e Testes 1 Simulador 58 minutos 7 Simuladores 11 minutos https://github.com/linkedin/bluepill
  46. 46. Obrigado! 49 Peça seu cartão! bit.ly/devcamp-nubank bit.ly/nubank-jobs Estamos contratando!

×