53
Functional Core Reactive Shell YOW! West 2016 @mokagio

Functional Core, Reactive Shell

Embed Size (px)

Citation preview

Page 1: Functional Core, Reactive Shell

Functional CoreReactive Shell

YOW! West 2016

@mokagio

Page 2: Functional Core, Reactive Shell

Spaghetti

Page 3: Functional Core, Reactive Shell

Spaghetti Architecture

Page 4: Functional Core, Reactive Shell

Lasagna

Page 5: Functional Core, Reactive Shell

Lasagna Architecture

Page 6: Functional Core, Reactive Shell

Ravioli

Page 7: Functional Core, Reactive Shell

Ravioli Architecture

Page 8: Functional Core, Reactive Shell

Pizza

Page 9: Functional Core, Reactive Shell

Back to thelasagna...

Page 10: Functional Core, Reactive Shell

Consumer POV

Page 11: Functional Core, Reactive Shell

Unit Tests POV

Page 12: Functional Core, Reactive Shell

Dependencies Tree

Page 13: Functional Core, Reactive Shell

Side Effects

Page 14: Functional Core, Reactive Shell

Stubs & Mocks

Page 15: Functional Core, Reactive Shell

StubTest Double

Indirect Inputs

Page 16: Functional Core, Reactive Shell

Use a stub for the dependencies

Page 17: Functional Core, Reactive Shell

MockTest Double

Indirect Outputs

Page 18: Functional Core, Reactive Shell

Use a mock to verify a side effect

Page 19: Functional Core, Reactive Shell

Is this a goodidea?

Page 20: Functional Core, Reactive Shell

Mocks Tell Lies

Page 21: Functional Core, Reactive Shell

NotProduction

Code!

Page 22: Functional Core, Reactive Shell

func sum(a: Int, b: Int) ->Int

Page 23: Functional Core, Reactive Shell

mock(+)or

expect(sum(1, 2)) == 3

Page 24: Functional Core, Reactive Shell

Ken Scambler"To Kill a Mockingtest"

"Mocks & Stubs"

Page 25: Functional Core, Reactive Shell

Many Dependencies

&

Side Effects

Page 26: Functional Core, Reactive Shell

Gary BernhardtBoundaries

Page 27: Functional Core, Reactive Shell

Objects >> Values

Page 28: Functional Core, Reactive Shell

func sum(a: Int, b: Int) ->Int

Page 29: Functional Core, Reactive Shell

Pure Function

Page 30: Functional Core, Reactive Shell

But I do need I/O...

Page 31: Functional Core, Reactive Shell

Decisionvs

Action

Page 32: Functional Core, Reactive Shell

ExampleInsert object in DB if

<condition>

Page 33: Functional Core, Reactive Shell

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

Page 34: Functional Core, Reactive Shell

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 }

Page 35: Functional Core, Reactive Shell

Split Approach: Decision

Easy to test using in memory values

No DB setup needed

Really does only one thing

Page 36: Functional Core, Reactive Shell

Split Approach: Actionclass DatabaseService { func performAction(action: DBAction) { // for each object switch on mode and perform mode action } }

Page 37: Functional Core, Reactive Shell

Split Approach: Action

Testable using simple scenarios

"Once and for all"

Page 38: Functional Core, Reactive Shell

Toppings

Page 39: Functional Core, Reactive Shell

Functional CoreImperative Shell

Gary Bernhardt

Page 40: Functional Core, Reactive Shell

Functional Core, Imperative Shell

Page 41: Functional Core, Reactive Shell

Pizza!

Page 42: Functional Core, Reactive Shell

Functional CoreImperative ShellReactive Shell

Page 43: Functional Core, Reactive Shell

Functional Core, Reactive Shell

Page 44: Functional Core, Reactive Shell

ExampleApp fetching stuff from

network and cache

Page 45: Functional Core, Reactive Shell

Functional Corestruct Stuff { let id: String let text: String let number: Int }

extension Stuff { init?(json: [String: AnyObject]) { /* ... */ }}

extension Stuff { init(realmObject: RealmStuff) { /* ... */ } }

Page 46: Functional Core, Reactive Shell

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

Page 47: Functional Core, Reactive Shell

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)" } } }

Page 48: Functional Core, Reactive Shell

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

Page 49: Functional Core, Reactive Shell

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()

Page 50: Functional Core, Reactive Shell

Good Idea?

Page 51: Functional Core, Reactive Shell

ConsLearning curve

Long and awkward reactive shell

Page 52: Functional Core, Reactive Shell

ProsReactive shell tells the story

Higher code mobility

Learning is GOOD 

Page 53: Functional Core, Reactive Shell

Gio@mokagio

http://mokacoding.com