Upload
pablo-villar
View
218
Download
1
Embed Size (px)
Citation preview
www.erlang-solutions.com
SWIFTBA MEETUP
JUNE 8th, 2016@ Inaka's Offices
www.erlang-solutions.com
HELLO!
Pablo Luciano [email protected]
@pablolvillar
www.erlang-solutions.com www.inaka.net
_iOS dev since 2011
_swifter since 2015
www.erlang-solutions.com
#warilis
www.erlang-solutions.com
writingarestinterconnectionlibraryinswift_
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
ServerBackend
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
ServerBackend
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
ServerBackend
www.erlang-solutions.com
Delete this user: [...]
200: Success,
user deleted
Why would we need a REST interconnection library?
Our App
ServerBackend
www.erlang-solutions.com
Why would we need a REST interconnection library?
C/R/U/D
Response
Our App
ServerBackend
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.ARCHITECTURALCONCEPT
www.erlang-solutions.com
GOAL: Provide a neat API
that our ViewControllers can interact withwithout 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
CreateReadUpdateDelete
And that place is called…
www.erlang-solutions.com
UserRepository
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
PostRepository
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
UserRepository
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 NSURLSessionBackend
GET/users NSURLSession
code
www.erlang-solutions.com
Repository AlamofireBackend
GET/users Alamofire
code
www.erlang-solutions.com
Repository TestingBackend
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
UserRepository
NSURLSessionBackend
GET/users
UsersListViewController .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…
Swift code!
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.
Serverlinking
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()
DictionaryRepresentable
www.erlang-solutions.com
OK, let's talk about parsing...
User[String: AnyObject]
read()
From the server
DictionaryInitializable
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!
@pablolvillar