Escape from Mars

45
Jorge D. Ortiz-Fuentes @jdortiz Escape from Mars Thank your Architecture #SwiftMars

Transcript of Escape from Mars

Jorge D. Ortiz-Fuentes @jdortiz

Escape from Mars Thank your Architecture

#SwiftMars

A Canonical Examples production

#SwiftMars

#SwiftMars

Agenda

Learning from NASA

SOLID Architecture

Design patterns

Learning from NASA

Agility and the MVP

Always grow

NO SPoF

Know where problems come from

Last long

Teams can work together

Shit happens

Do you have any problems?

#SwiftMars

Needs to Address

Fast growth

Robustness

• testable

• decoupled

• debuggable (blame)

Team collaboration

Reusable

Defer decisions

Replaceable frameworks

There is a way: SOLID Principles

AKA Clean

Architecture

Impossible to grow anything in Mars?

SOLID Architecture

App

Architecture?View Controller

View Controller

View Controller

View Controller

Sys

tem

Fram

ewor

ks

ArchitectureView Controller

View Controller

View Controller

View Controller

Sys

tem

Fram

ewor

ks

System

Frameworks

View Controller

View ControllerView Controller ModelPresenterViewModel

Persistance FW

View

Presenter

Entity Gateway

Clean Architecture

Interactor

Entity

Net

wor

k

Loca

tion

FW

Clean ArchitectureAppDelegate

View (VC) Presenter Interactor Entity Gateway

Connector

#SwiftMars

Single Responsibility

MVC is not enough

More classes, but more cohesive: only one reason to change

#SwiftMars

Business Logicextension ShowAllSpeakersInteractor: InteractorCommandProtocol { func execute() { let entities = entityGateway.fetchAllSpeakers() let displayData = entities.map({entity in return SpeakerDisplayData(speaker: entity)}) presenter?.presentAllSpeakers(displayData) } }

#SwiftMars

Open Close

Open to Extension, Closed to Modification

#SwiftMars

Passive Viewclass SpeakersTableViewController: UITableViewController, SegueHandlerTypeProtocol {

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return numberOfRows }

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(SpeakersTableViewController.speakerCellIdentifier, forIndexPath: indexPath) as! SpeakerTableViewCell eventHandler?.presentCell(cell, indexPath: indexPath) return cell } }

#SwiftMars

Liskov Substitution

It should be possible to use derived classes without special care

#SwiftMars

Presentationclass SpeakersListsPresenter { weak var view: SpeakersListViewProtocol? let interactor: ShowAllSpeakersInteractor private var speakers: [SpeakerDisplayData]=[] }

extension SpeakersListsPresenter: SpeakersListPresenterProtocol { func presentAllSpeakers(speakers: [SpeakerDisplayData]) { view?.configureListWithNumberOfRows(speakers.count) self.speakers = speakers let addedIndexPaths = speakers.enumerate() .map({(index, _) in return NSIndexPath(forRow: index, inSection: 0)}) view?.addRowsAtIndexPaths(addedIndexPaths) } }

#SwiftMars

Dependency Inversion

The business case SHOULDN’T depend on the frameworks

#SwiftMars

Deletion Use Caseclass DeleteSpeakerInteractor { let entityGateway: EntityGatewayDeleteSpeakerProtocol var id: String?

init(entityGateway: EntityGatewayDeleteSpeakerProtocol) { self.entityGateway = entityGateway } }

extension DeleteSpeakerInteractor : InteractorCommandProtocol { func execute() { guard let id = self.id else { return } entityGateway.deleteSpeaker(id) } }

#SwiftMars

Interface Segregation

Don’t force a class to depend on methods that it won’t use

One interactor will not likely use every functionality of the entity gateway

#SwiftMars

class InMemorySpeakersRepo { }

extension InMemorySpeakersRepo: EntityGatewayFetchSpeakersProtocol { func fetchAllSpeakers() -> [Speaker] { … } }

extension InMemorySpeakersRepo: EntityGatewayCreateSpeakerProtocol { func createSpeaker(name name: String, title: String, synopsis: String, dateSubmitted: NSDate) { … } }

extension InMemorySpeakersRepo: EntityGatewayDeleteSpeakerProtocol { func deleteSpeaker(id: String) { … } }

Different Functions of the Entity Gateway

Persistance FW

View

Presenter

Entity Gateway

Clean Architecture

Interactor

Entity

Net

wor

k

Loca

tion

FW

Design Patterns

Take me Outta Here

func presentCell(cell: SpeakerCellProtocol, indexPath: NSIndexPath) { let index = indexPath.row guard index < speakers.count else { return } let speaker = speakers[index] cell.displayName(speaker.name) cell.displayTitle(speaker.title) }

Know That!

#SwiftMars

Ready for Lifeclass Houston { func launchSpaceship() throws { guard fuel >= 1.0 else { throw LaunchError.NoFuel } guard astronaut != "" else { throw LaunchError.NoAstronaut } guard spaceshipOK else { throw LaunchError.BrokenShip("Engine") } print("Launching spaceship") } } var control = Houston(fuel: 1.0, astronaut: nil, spaceshipOK: true) do { try control.launchSpaceship() } catch Houston.LaunchError.NoFuel { print("Adding fuel") } catch Houston.LaunchError.NoAstronaut { print("Next in line") } catch Houston.LaunchError.BrokenShip(let problem) { print(problem) } catch let unknowError { // }

Know That!

#SwiftMars

Null Objectclass Sample { let identifier: String let biohazard: Bool func check() { let result = (biohazard ? "Care with sample \(identifier)" : "OK") print(result) } }

func fetchSample() -> Sample? { return nil }

if let sample = fetchSample() { sample.check() } else { print("OK") }

#SwiftMars

Null Objectclass Sample { let identifier: String let biohazard: Bool /* … */ }

class NullSample: Sample { init() { super.init(identifier: "", biohazard: false) } override func check() { print("OK (empty)") } }

func fetchSample() -> Sample { return NullSample() }

let sample = fetchSample() sample.check()

Poor Templateclass PoorAstronaut { func getIntoSpaceShip() { print("Commencing countdown engines on") }

func travelTo(destination: String) { fatalError() }

func comeBack() { print("Planet earth is blue") }

func performMissionTo(destination: String) { getIntoSpaceShip() travelTo(destination) comeBack() } }

let astronaut = PoorAstronaut() astronaut.performMissionTo("Moon")

Swifty Templateprotocol Mission { func getIntoSpaceShip() func travelTo(destination: String) func comeBack() func performMissionTo(destination: String) }

extension Mission { func getIntoSpaceShip() { print("Commencing countdown engines on") } func comeBack() { print("Planet earth is blue") } func performMissionTo(destination: String) { getIntoSpaceShip() travelTo(destination) comeBack() } }

class Astronaut: Mission { func travelTo(destination: String) { print("I always wanted to visit \(destination)") } }

let markWatney = Astronaut() markWatney.performMissionTo("Mars")

Next Steps

#SwiftMars

Recommendations

Pay attention to your architecture; It always pays off

Use Principles to take decisions

Take advantage of the language and adapt the patterns to it

In case of doubt, ask for an architecture loan and ship it

canonicalexamples.com coupon:

CODEMOTIONVIII

Thank you!

@jdortiz #SwiftMars