Upload
giovanni-lodi
View
23.022
Download
0
Embed Size (px)
Citation preview
Functional CoreReactive Shell
YOW! West 2016
@mokagio
Spaghetti
Spaghetti Architecture
Lasagna
Lasagna Architecture
Ravioli
Ravioli Architecture
Pizza
Back to thelasagna...
Consumer POV
Unit Tests POV
Dependencies Tree
Side Effects
Stubs & Mocks
StubTest Double
Indirect Inputs
Use a stub for the dependencies
MockTest Double
Indirect Outputs
Use a mock to verify a side effect
Is this a goodidea?
Mocks Tell Lies
NotProduction
Code!
func sum(a: Int, b: Int) ->Int
mock(+)or
expect(sum(1, 2)) == 3
Ken Scambler"To Kill a Mockingtest"
"Mocks & Stubs"
Many Dependencies
&
Side Effects
Gary BernhardtBoundaries
Objects >> Values
func sum(a: Int, b: Int) ->Int
Pure Function
But I do need I/O...
Decisionvs
Action
ExampleInsert object in DB if
<condition>
Standard Approachclass 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 } }
Split Approach: Decisionstruct 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 }
Split Approach: Decision
Easy to test using in memory values
No DB setup needed
Really does only one thing
Split Approach: Actionclass DatabaseService { func performAction(action: DBAction) { // for each object switch on mode and perform mode action } }
Split Approach: Action
Testable using simple scenarios
"Once and for all"
Toppings
Functional CoreImperative Shell
Gary Bernhardt
Functional Core, Imperative Shell
Pizza!
Functional CoreImperative ShellReactive Shell
Functional Core, Reactive Shell
ExampleApp fetching stuff from
network and cache
Functional Corestruct Stuff { let id: String let text: String let number: Int }
extension Stuff { init?(json: [String: AnyObject]) { /* ... */ }}
extension Stuff { init(realmObject: RealmStuff) { /* ... */ } }
Functional Coredescribe("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": anyNumber]
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 } }
Functional Coreextension 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)" } } }
Side Effect Codeclass 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) } } }
Reactive Shellmerge([ 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()
Good Idea?
ConsLearning curve
Long and awkward reactive shell
ProsReactive shell tells the story
Higher code mobility
Learning is GOOD
Gio@mokagio
http://mokacoding.com