Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Functional Core, Reactive Shell

31.138 Aufrufe

Veröffentlicht am

Software written in such way might look unconventional, and has a bit of a learning curve. The talk is to show that the advantages it brings make it a viable choice.

Veröffentlicht in: Software

Functional Core, Reactive Shell

  1. 1. Functional Core Reactive Shell YOW! West 2016 @mokagio
  2. 2. Spaghetti
  3. 3. Spaghetti Architecture
  4. 4. Lasagna
  5. 5. Lasagna Architecture
  6. 6. Ravioli
  7. 7. Ravioli Architecture
  8. 8. Pizza
  9. 9. Back to the lasagna...
  10. 10. Consumer POV
  11. 11. Unit Tests POV
  12. 12. Dependencies Tree
  13. 13. Side Effects
  14. 14. Stubs & Mocks
  15. 15. Stub Test Double Indirect Inputs
  16. 16. Use a stub for the dependencies
  17. 17. Mock Test Double Indirect Outputs
  18. 18. Use a mock to verify a side effect
  19. 19. Is this a good idea?
  20. 20. Mocks Tell Lies
  21. 21. Not Production Code !
  22. 22. func sum(a: Int, b: Int) -> Int
  23. 23. mock(+) or expect(sum(1, 2)) == 3
  24. 24. Ken Scambler "To Kill a Mockingtest" "Mocks & Stubs"
  25. 25. Many Dependencies & Side Effects
  26. 26. Gary Bernhardt Boundaries
  27. 27. Objects >> Values
  28. 28. func sum(a: Int, b: Int) -> Int
  29. 29. Pure Function
  30. 30. But I do need I/O...
  31. 31. Decision vs Action
  32. 32. Example Insert object in DB if <condition>
  33. 33. Standard Approach class DatabaseService { func insertObjects(objects: [DBObject], updatedAfter date: NSDate) { // 1. Filter objects array based on criteria // 2. For each remainig object // 2.1 Insert object in DB } }
  34. 34. Split Approach: Decision struct DBAction { enum Mode { case Insert case Update case Delete } let objects: [DBObject] let mode: Mode } func persistObjectsAction(objects: [DBObject], updatedAfter updatedDate: NSDate) -> // 1. Filter object based on date // 2. Create action value using objects and insert mode }
  35. 35. Split Approach: Decision Easy to test using in memory values No DB setup needed Really does only one thing
  36. 36. Split Approach: Action class DatabaseService { func performAction(action: DBAction) { // for each object switch on mode and perform mode action } }
  37. 37. Split Approach: Action Testable using simple scenarios "Once and for all"
  38. 38. Toppings
  39. 39. Functional Core Imperative Shell Gary Bernhardt
  40. 40. Functional Core, Imperative Shell
  41. 41. Pizza!
  42. 42. Functional Core Imperative Shell Reactive Shell
  43. 43. Functional Core, Reactive Shell
  44. 44. Example App fetching stuff from network and cache
  45. 45. Functional Core struct Stuff { let id: String let text: String let number: Int } extension Stuff { init?(json: [String: AnyObject]) { /* ... */ } } extension Stuff { init(realmObject: RealmStuff) { /* ... */ } }
  46. 46. Functional Core describe("Stuff from JSON dictionary") { context("when the dictionary contains all the valid keys") { it("it returns an instance configured with the values in the dictionary") { let anyId = "any id" let anyText = "any text" let anyNumber = 42 let dict: [String: AnyObject] = ["id": anyId, "text": anyText, "number": anyNum let stuff = Stuff(json: dict) expect(stuff?.id) == anyId expect(stuff?.text) == anyText expect(stuff?.number) == anyNumber } } } describe("Stuff model from Realm object") { it("sets its properties based on the Realm object one") { let realmObject = RealmStuff.test_fixture() let sut = Stuff(realmObject: realmObject) expect(sut.id) == realmObject.id expect(sut.number) == realmObject.number expect(sut.text) == realmObject.text } }
  47. 47. Functional Core extension CellViewModel { init(stuff: Stuff) { self.text = "(stuff.id) - (stuff.text) ((stuff.number))" } } describe("CellViewModel") { context("when initialized with a Stuff model") { it("sets the text using the stuff properties") { let stuff = Stuff(id: "123", text: "any text", number: 42) let sut = CellViewModel(stuff: stuff) expect(sut.text) == "123 - any text (42)" } } }
  48. 48. Side Effect Code class ViewController: UIViewController { enum Effect { case UpdateView(viewModels: [CellViewModel]) case PresentAlert(error: ErrorType) } func performEffect(effect: Effect) { switch effect { case .UpdateView(let viewModels): self.viewModels = viewModels tableView.reloadData() case .PresentAlert(let error): presentErrorAlert(error) } } }
  49. 49. Reactive Shell merge([ databaseService.allStuff() .map { $0.map { Stuff(realmObject: $0) } }, networkService.performRequest(toEndpoint: .GetStuff) .flatMapLatest { JSON in return Stuff.stuffProducer(withJSON: JSON) } ]) .map { stuffArray in stuffArray.map { CellViewModel(stuff: $0) } } .map { viewModels in Effect.UpdateView(viewModels: viewModels) } .observeOnMainThread() .on( failed: { [weak self] error in self?.performEffect(Effect.PresentAlert(error: error)) }, next: { [weak self] effect in self?.performEffect(effect) } ) .start()
  50. 50. Good Idea?
  51. 51. Cons Learning curve Long and awkward reactive shell
  52. 52. Pros Reactive shell tells the story Higher code mobility Learning is GOOD 
  53. 53. Gio @mokagio http://mokacoding.com

×