Functional Core
Reactive Shell
YOW! West 2016
@mokagio
Spaghetti
Spaghetti Architecture
Lasagna
Lasagna Architecture
Ravioli
Ravioli Architecture
Pizza
Back to the
lasagna...
Consumer POV
Unit Tests POV
Dependencies Tree
Side Effects
Stubs & Mocks
Stub
Test Double
Indirect Inputs
Use a stub for the dependencies
Mock
Test Double
Indirect Outputs
Use a mock to verify a side effect
Is this a good
idea?
Mocks Tell Lies
Not
Production
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 Bernhardt
Boundaries
Objects >> Values
func sum(a: Int, b: Int) ->
Int
Pure Function
But I do need I/O...
Decision
vs
Action
Example
Insert object in DB if
<condition>
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
}
}
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
}
Split Approach: Decision
Easy to test using in memory values
No DB setup needed
Really does only one thing
Split Approach: Action
class 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 Core
Imperative Shell
Gary Bernhardt
Functional Core, Imperative Shell
Pizza!
Functional Core
Imperative Shell
Reactive Shell
Functional Core, Reactive Shell
Example
App fetching stuff from
network and cache
Functional Core
struct Stuff {
let id: String
let text: String
let number: Int
}
extension Stuff {
init?(json: [String: AnyObject]) { /* ... */ }
}
extension Stuff {
init(realmObject: RealmStuff) { /* ... */ }
}
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
}
}
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)"
}
}
}
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)
}
}
}
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()
Good Idea?
Cons
Learning curve
Long and awkward reactive shell
Pros
Reactive shell tells the story
Higher code mobility
Learning is GOOD 
Gio
@mokagio
http://mokacoding.com

Functional Core, Reactive Shell