Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The benefits of MQTT in remote debugging

The benefits of MQTT in remote debugging

Talk by Danil Lisovoy.

Данил расскажет, почему удобно писать на Redux, и покажет как легко с помощью MQTT добиться тестируемости своей архитектуры на Redux.

This talk was made for CocoaFriday #1 ( https://cocoaheads.org.ua/cocoafriday/1 ) which took place Mar 1, 2019.

Video is available here: https://youtu.be/3HGou0SS1qQ

CocoaHeads Ukraine

March 01, 2019
Tweet

More Decks by CocoaHeads Ukraine

Other Decks in Programming

Transcript

  1. The benefits of MQTT
    in remote debugging

    View full-size slide

  2. State
    Store
    Action
    State
    Reduce

    View full-size slide

  3. Actions
    struct DidChangeLocationPermission: AppEvent {
    let access: Bool
    }
    struct RequestUpdateLocation: AppEvent {}
    struct UpdateLocation: AppEvent {}
    struct DidUpdateLocation: AppEvent {
    let timeStamp: TimeInterval
    let location: Result
    }

    View full-size slide

  4. import Foundation
    import ReactiveSwift
    import Result
    public protocol Defaultable {
    static var defaultValue: Self { get }
    }
    infix operator <~ : BindingPrecedence
    open class Store {
    public typealias Reducer = (State, Event) -> State
    fileprivate var innerProperty: MutableProperty
    fileprivate var reducers: [Reducer]
    public required init(state: State, reducers: [Reducer]) {
    self.innerProperty = MutableProperty(state)
    self.reducers = reducers
    }
    public func consume(event: Event) {
    self.innerProperty.value = reducers.reduce(self.innerProperty.value) { $1($0, event) }
    }
    }
    extension Store: PropertyProtocol {
    public var value: State {
    return innerProperty.value
    }
    public var producer: SignalProducer {
    return innerProperty.producer
    }
    public var signal: Signal {
    return innerProperty.signal
    }
    }
    public extension Store {
    @discardableResult
    public static func <~ (target: Store, source: Source) -> Disposable?
    where Event == Source.Value
    {
    return source.producer
    .take(during: target.innerProperty.lifetime)
    .startWithValues(target.consume)
    }
    }
    Store

    View full-size slide

  5. struct AppState: Encodable {
    let location: AppLocation
    let weather: AppWeather
    let searching: AppSearch
    let sync: AppSync
    let photos: AppPhotos
    }
    extension AppState: Defaultable {
    static var defaultValue = AppState(
    location: .defaultValue,
    weather: .defaultValue,
    searching: .defaultValue,
    sync: .defaultValue,
    photos: .defaultValue)
    }
    extension AppState {
    static func reudce(_ state: AppState, _ event: AppEvent) -> AppState {
    return AppState(
    location: AppLocation.reudce(state.location, event),
    weather: AppWeather.reudce(state.weather, event),
    searching: AppSearch.reudce(state.searching, event),
    sync: AppSync.reduce(state.sync, event),
    photos: AppPhotos.reduce(state.photos, event)
    )
    }
    }

    View full-size slide

  6. struct AppLocation: Encodable, Equatable {
    let availability: Availability; enum Availability: String, Encodable, Equatable {
    case notYetRequested
    case requested
    case notAvailable
    case available
    }
    let deviceLocation: DeviceLocation; enum DeviceLocation: AutoEncodable, Equatable {
    case none
    case updating
    case success(location: Coordinates2D, timestamp: TimeInterval)
    case error(value: AnyError)
    }
    }
    AppLocation

    View full-size slide

  7. ViewModel
    View
    Render
    O
    bserves
    Service

    View full-size slide

  8. ViewModel
    View
    Render
    O
    bserves
    Store

    View full-size slide

  9. State
    Service
    Action
    Dispatches
    O
    bserves
    State
    Store
    API

    View full-size slide

  10. Action
    creator
    Action
    Action
    Action
    Store
    Service

    View full-size slide

  11. MQTT
    What about debugging?

    View full-size slide

  12. Broker
    P
    P
    P
    C
    C
    C

    View full-size slide

  13. Is it secure?

    View full-size slide

  14. P
    State
    Store UI
    Props
    B
    B
    B
    Action

    View full-size slide

  15. Broker
    Actions
    Props
    State
    Examples

    View full-size slide

  16. Protocol
    import ReactiveSwift
    import Swinject
    import struct Result.AnyError
    import enum Result.NoError
    protocol MQTTServiceProtocol {
    func publish(in channel: String)
    -> Action where T: Encodable
    func subscribe(to channel: String)
    -> Signal where T: Decodable
    }
    protocol MQTTServiceAccessible {
    var mqttService: MQTTServiceProtocol { get }
    }
    extension MQTTServiceAccessible {
    var mqttService: MQTTServiceProtocol {
    return Container.current
    .resolve(MQTTServiceProtocol.self)!
    }
    }

    View full-size slide

  17. extension MQTTService: MQTTServiceProtocol {
    func publish(in channel: String)
    -> Action
    where T: Encodable
    {
    return Action(execute: publishClosure(in: channel))
    }
    func publishClosure(in channel: String) ->
    (T) -> SignalProducer<(), AnyError>
    where T: Encodable
    {
    return { [weak self] message in
    var data: Data
    guard let self = self else { return .empty }
    do { data = try JSONEncoder().encode(message) }
    catch { return SignalProducer(error: AnyError(error)) }
    self.client.publish(.init(
    topic: channel,
    payload: .init(data)))
    return .empty
    }
    }
    ..

    }
    Store
    B

    View full-size slide

  18. Store
    B
    class MQTTMiddleware: StoreMiddleware, MQTTServiceAccessible {
    private let flags: LoggerFlags
    private let name: String
    private lazy var logEvent: Action =
    mqttService.publish(in: "\(name)/store/event")
    private lazy var logState: Action =
    mqttService.publish(in: "\(name)/store/state")
    private let events = Signal.pipe()
    private let states = Signal.pipe()
    public init(
    flags: LoggerFlags = .logAll,
    name: String = "undefined"
    )
    {
    self.flags = flags
    self.name = name
    setupObserving()
    }
    func consume(event: Event) -> SignalProducer? {
    return SignalProducer(value:
    TypeDispatcher.value(event)
    .dispatch { (event: AppEvent) in events.input.send(value: event) }
    .extract()
    )
    }
    func stateDidChange(state: State) {
    TypeDispatcher.value(state)
    .dispatch { (state: AppState) in states.input.send(value: state) }
    }
    func unsafeValue() -> Signal? { return nil }
    }
    private typealias Observing = MQTTMiddleware
    private extension Observing {
    func setupObserving() {
    logEvent <~ events.output.throttle(0.5, on: QueueScheduler.main).map(AnyEvent.init)
    logState <~ states.output.throttle(0.5, on: QueueScheduler.main)
    }
    }

    View full-size slide

  19. Sourcery
    import Foundation
    <%
    func camelCased(_ string: String) -> String {
    return "\(String(string.first!).lowercased())\(String(string.dropFirst()))".replacingOccurrences(of: ".", with: "")
    }
    func allEvents() -> [Type] {
    return types.all.filter { type in
    (type is Struct || type is Enum) && (type.implements["AppEvent"] != nil)
    }
    }
    -%>
    enum AnyEvent: Codable {
    <%_ for type in allEvents() { -%>
    case <%= camelCased(type.name) %>(<%= type.name %>)
    <%_ } -%>
    init(_ event: AppEvent) {
    switch event {
    <%_ for type in allEvents() { -%>
    case let event as <%= type.name %>: self = .<%= camelCased(type.name) %>(event)
    <%_ } -%>
    default: fatalError("Unexpected event")
    }
    }
    var event: AppEvent {
    switch self {
    <%_ for type in allEvents() { -%>
    case let .<%= camelCased(type.name) %>(event): return event
    <%_ } -%>
    }
    }
    enum Keys: CodingKey {
    case type, event
    }
    enum EventType: String, Codable {
    <%_ for type in allEvents() { -%>
    case <%= camelCased(type.name) %>
    <%_ } -%>
    }
    init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: Keys.self)
    let type = try container.decode(EventType.self, forKey: .type)
    switch type {
    <%_ for type in allEvents() { -%>
    case .<%= camelCased(type.name) %>: self = .<%= camelCased(type.name) %>(try container.decode(<%= type.name %>.self, forKey: .event))
    <%_ } -%>
    }
    }
    func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: Keys.self)
    switch self {
    <%_ for type in allEvents() { -%>
    case .<%= camelCased(type.name) %>(let event):
    try container.encode(EventType.<%= camelCased(type.name) %>, forKey: .type)
    try container.encode(event, forKey: .event)
    <%_ } -%>
    }
    }
    }

    View full-size slide

  20. Demo
    Weather App

    View full-size slide