Functional Core, Reactive Shell

  • View
    23.022

  • Download
    0

  • Category

    Software

Preview:

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

Recommended