www.erlang-solutions.com SWIFTBA MEETUP JUNE 8th, 2016 @ Inaka's Offices
www.erlang-solutions.com HELLO! Pablo Luciano Villar pablo@inakanetworks.com @pablolvillar www.erlang-solutions.com www.inaka.net _iOS dev since 2011 _swifter since 2015
www.erlang-solutions.com #warilis
www.erlang-solutions.com writing a rest interconnection library in swift_
www.erlang-solutions.com ROADMAP 1. Motivation 2. Architectural concept 3. Implementation
www.erlang-solutions.com 1. MOTIVATION
www.erlang-solutions.com Why would we need a REST interconnection library? Our App Server Backend Give me a list with all the users 200: Success, returns JSON array with users
www.erlang-solutions.com Why would we need a REST interconnection library? Create a user with this info: [...] 200: Success, user created Our App Server Backend
www.erlang-solutions.com Why would we need a REST interconnection library? Update some info for this user: [...] 200: Success, returns user updated Our App Server Backend
www.erlang-solutions.com Delete this user: [...] 200: Success, user deleted Why would we need a REST interconnection library? Our App Server Backend
www.erlang-solutions.com Why would we need a REST interconnection library? C/R/U/D Response Our App Server Backend
www.erlang-solutions.com ● NSURLSession ● Alamofire / AFNetworking ● Other libraries How we usually write networking code:
www.erlang-solutions.com NSURLSession example: "Give me a list with all the users" GET /users How we usually write networking code:
www.erlang-solutions.com Now, where should that networking code go…?
www.erlang-solutions.com Premises: ● Find a good place to put our networking code. ● Define a proper way to architecture our REST layer. ● Avoid duplicated code from common CRUD operations. ● Come up with a concise API that we can use across all of our projects.
www.erlang-solutions.com 2.ARCHITECTURAL CONCEPT
www.erlang-solutions.com GOAL: Provide a neat API that our ViewControllers can interact with without having to worry about networking implementation details.
www.erlang-solutions.com Let's find out common paths... "Give me a list with all the users" GET /users "Give me the details for this user" GET /users/:id "Create a user with this data" POST /users "Update this user with this data" PUT /users/:id "Delete this user" DELETE /users/:id
www.erlang-solutions.com Let's find out common paths... "Give me a list with all the users" GET /users "Give me the details for this user" GET /users/:id "Create a user with this data" POST /users "Update this user with this data" PUT /users/:id "Delete this user" DELETE /users/:id
www.erlang-solutions.com Let's find out common paths... "Give me a list with all the posts" GET /posts "Give me the details for this post" GET /posts/:id "Create a post with this data" POST /posts "Update this post with this data" PUT /posts/:id "Delete this post" DELETE /posts/:id
www.erlang-solutions.com
www.erlang-solutions.com "Give me a list with all the entities" GET /:name "Give me the details for this entity" GET /:name/:id "Create an entity with this data" POST /:name "Update this entity with this data" PUT /:name/:id "Delete this entity" DELETE /:name/:id Let's introduce the entity concept...
www.erlang-solutions.com "Give me a list with all the entities" GET /:name "Give me the details for this entity" GET /:name/:id "Create an entity with this data" POST /:name "Update this entity with this data" PUT /:name/:id "Delete this entity" DELETE /:name/:id Notice that any entity needs an id to work
www.erlang-solutions.com User Post Comment id Create? Read? Update? Delete? Entity
www.erlang-solutions.com REPOSITORY! Actually, we need a different place from which we can perform CRUD operations to our entity… Entity Create Read Update Delete And that place is called…
www.erlang-solutions.com User Repository User findAll() → [User] findByID(id) → User create(User) update(User) delete(User)
www.erlang-solutions.com Elemental CRUD operations* Repository Entity findAll() → [Entity] findByID(id) → Entity create(Entity) update(Entity) delete(Entity)
www.erlang-solutions.com Post Repository Post findAll() → [Post] findByID(id) → Post create(Post) update(Post) delete(Post) Customization findPostsFromUser(User) → [Post]
www.erlang-solutions.com *
www.erlang-solutions.com This one has the issue that every repository you create will come with all these basic CRUD methods by default, even if any particular repository doesn't need all of them. That breaks the YAGNI principle. But still, it's a very convenient approach for us. * There are many ways to approach the Repository pattern.
www.erlang-solutions.com name Our Repository needs a name to work Repository Entity findAll() → [Entity] findByID(id) → Entity create(Entity) update(Entity) delete(Entity) GET /:name/ GET /:name/:id POST /:name PUT /:name/:id DELETE /:name/:id
www.erlang-solutions.com User Repository findAll() → [User] findByID(id) → User create(User) update(User) delete(Entity) GET /users/ GET /users/:id POST /users PUT /users/:id DELETE /users/:id This name represents the group of entities that the repository works with "users" User
www.erlang-solutions.com Last, but not least… We still need to define a place where our networking code will fall into. Let's introduce the concept of… BACKEND
www.erlang-solutions.com Repository Backend GET /users Networking code
www.erlang-solutions.com Repository NSURLSession Backend GET /users NSURLSession code
www.erlang-solutions.com Repository Alamofire Backend GET /users Alamofire code
www.erlang-solutions.com Repository Testing Backend GET /users Sample code (mocks)
www.erlang-solutions.com Repository Backend GET /users Networking code Raw Data Parsing??
www.erlang-solutions.com Raw Data Parsing User Repository NSURLSession Backend GET /users UsersList ViewController .findAll() Users
www.erlang-solutions.com 3.IMPLEMENTATION
www.erlang-solutions.com Let's translate all these diagrams into code. Not just any code, but… Swiftcode!
www.erlang-solutions.com Where to start…? OK, let's do Repository
www.erlang-solutions.com ✔ Protocol composition ✔ Generics - Prefer composition over inheritance whenever possible. - This way, you end up having a more flexible and extendable architecture. - By using generics, you lead your models to become more adaptive and customizable. - The final user is going to be happier.
www.erlang-solutions.com Before proceeding, let's introduce two new friends you should become familiar with... ● Result ● Future
www.erlang-solutions.com ● Result - It's an enum that we're going to use to represent a result. - It can be either: - Success (and hold a relevant result value) - Failure (and hold a relevant error)
www.erlang-solutions.com ● Result e. g. [User] e. g. NSError
www.erlang-solutions.com ● Future - You should definitely check out this talk: - https://realm.io/news/swift-summit-javier-soto-futures/ - Anyway, roughly speaking, a Future is a struct that we're going to use to represent the computation of an asynchronous task.
www.erlang-solutions.com - Here you can see why it's convenient to use Futures:
www.erlang-solutions.com
www.erlang-solutions.com .map and .andThen are 2 special functions in Futures that allow us to chain asynchronous operations very nicely.
www.erlang-solutions.com ● Future - Let's create a function that returns a Future: This Future is going to work with a Result<[User], NSError>
www.erlang-solutions.com - Let's use that Future: let result: (Result<[User], NSError>) Type inference let users: [User] let error: NSError
www.erlang-solutions.com ✔ Static typing / Type inference ✔ Enums with associated values - Compiler enforces you to use the types you're expected to use. - Write less, know more. - We're getting all the juice from Swift enums by using them with associated values. - This allows us to define powerful structures to represent discrete values containing relevant information in a proper way.
www.erlang-solutions.com Now, let's add elemental CRUD functionality to our Repository...
www.erlang-solutions.com We can add default implementations for those in a protocol extension:
www.erlang-solutions.com Let's analyze how we would implement one of those…
www.erlang-solutions.com Let's add a Backend at the Repository level...
www.erlang-solutions.com Let's define a Backend: Associated type: String Means that any case will have a .rawValue of type String. "GET" "POST" "PUT" "DELETE" come by default
www.erlang-solutions.com Let's (finally) add a Backend at the Repository level...
www.erlang-solutions.com let future: Future<NSData?, NSError> Type inference ≠Parsing
www.erlang-solutions.com =
www.erlang-solutions.com
www.erlang-solutions.com Now, let's define a concrete Backend that we can actually use...
www.erlang-solutions.com Now, let's define a concrete Backend that we can actually use...
www.erlang-solutions.com URL checking We're going to improve this. Later. Promise. Server linking Dependency
www.erlang-solutions.com We need variables to hold these. Also, it would be cool to be able to inject them.
www.erlang-solutions.com Let's go back a bit to our NSURLSessionBackend definition... We need to hold state, so we need a class.
www.erlang-solutions.com Let's define our configuration object
www.erlang-solutions.com We can use constructor injection in our NSURLSessionBackend... With default values Remember: Using dependency injection helps unit-testing.
www.erlang-solutions.com ✔ Dependency Injection ✔ Enums with raw values - Initializers with default values in their parameters encourage constructor injection. - Once again, Swift eases unit-testing. - Enums can be associated to a type so that their cases hold a .rawValue of that type. - You can define their raw values by your own, or let the compiler do its magic and use the defaults.
www.erlang-solutions.com Now the question is, why the hell are we still using NSError??
www.erlang-solutions.com Alright, let's customize!
www.erlang-solutions.com Let's enhance our error handling... All of these do not appear as errors in the networking process... We usually get a success response with a status code that WE should interpret as an error, depending on the code (e.g. a 500).
www.erlang-solutions.com Now, let's talk about parsing... Do I have time to talk about this? YES / NO
www.erlang-solutions.com OK, let's talk about parsing... User[String: AnyObject] a.k.a. "Dictionary" conversion
www.erlang-solutions.com To the server OK, let's talk about parsing... User[String: AnyObject] create() update() Dictionary Representable
www.erlang-solutions.com OK, let's talk about parsing... User[String: AnyObject] read() From the server Dictionary Initializable
www.erlang-solutions.com From dictionary / To dictionary In case there's a parsing error (e.g. a missing field). Remember: You should NEVER initialize an invalid object.
www.erlang-solutions.com From dictionary, example:
www.erlang-solutions.com To dictionary, example:
www.erlang-solutions.com ✔ ErrorType ✔ Throws - Error handling has been enhanced in Swift. - Now you can define your own errors, which combined with the power of enums and pattern matching allow you to work in a cleaner way. - Throwable initializers encourage better exceptions handling mechanisms, such as try/catch. - As we saw, you can make your initializers throw any discrete ErrorType that you define.
www.erlang-solutions.com https://github.com/inaka/JaymeMeet Jayme ✓ Open Source ✓ Issues are welcome ✓ So are Pull Requests Jayme is basically what we just built, with some other enhancements that I haven't talked about because of time, for example: ● Pagination support (PagedRepository) ● More generalization (Backend is more abstract)
www.erlang-solutions.com Meet Jayme https://github.com/inaka/Jayme
www.erlang-solutions.com Meet Jayme https://github.com/inaka/Jayme
www.erlang-solutions.com THANK YOU! Any questions? pablo@inakanetworks.com @pablolvillar

Writing a REST Interconnection Library in Swift