Protocol Oriented Blockchain in Swift

2 of 3 — Mocking the Server

RNDM
10 min readMar 6, 2019

Welcome back! If you’ve made it this far, then you are really on a journey of discovery! In the last article, we built a system for creating a Protocol Oriented Blockchain in Swift.

When building out this solution, I felt it was important for us to look at how we can create a way to manage and maintain an instance of our Chain, as well as processing requests and returning results to the object requesting them. So for this part of our exploration, we will build out a mock version of a managing object which we will deem the Server and a requesting object, which we will deem a Client.

(TL;DR: If you are keen to get your hands on the working playground for this, you can do that through the RNDM Github Page here.)

Our Process

Before we begin, let’s take a quick look at what we are trying to achieve. We are still working towards the end goal of the tableview updating with each change to the chain. And for this stage, we are focusing on the rules that define the communication between the client request, the server processing and the chain responses. These rules look a little like the diagram below:

Walking through this:

1) The client makes a request to a specific URL

2) The Server processes this URL Request

3.a) If the request is invalid due to our defined business rules, it will serve an error

3.b) If the server processes a valid request, it sends the transaction to the Chain

4.a) The Chain processes the transaction as invalid it will serve an error

4.b) The Chain processes the transaction as valid it will serve the chain

5) The server will pass back whatever it receives from the Chain

6.a) The error is passed into the resolution, or

6.b) The chain is passed into the resolution

7) The resolution is fulfilled on the Client

This mechanism is what we will endeavour to build, making use of the tools we built in the last article.

Code

So now we know what we want to do, we can start to look at our code.

Resolution

As with the last article, we will start with the simplest item first. All of our endings result in a Resolution. For us this will be a simple construct: a closure providing the response and the number of the request.

// Final Resolution.swift
typealias Resolution = ((Response, Int) -> ())

Client

Four our Client, we are going to define a protocol. This is because we don’t want to explicitly define a Client object since potentially anything could be interested in having a conversation with our server.

protocol Client {
var server: Server { get }
var queue: DispatchQueue { get }
}

As you can see, our protocol is very simple. It will require to have an instance of our Server (to be created later) and a queue, so we can run background tasks.

Next, we can extend the functionality of our protocol to include the fetch mechanism to talk to our server:

extension Client {
func fetch(url string: String,
method: String = "GET",
body: Any? = nil,
latency: UInt32 = 0,
resolve: Resolution? = nil) {
guard let url = URL(string: string) else { return }
queue.asyncAfter(deadline: DispatchTime.now() +
DispatchTimeInterval.seconds(Int(latency))) {
let request: URLRequest = {
var request = URLRequest(url: url)
request.httpMethod = method
if let body = body,
let data = try? JSONSerialization.data(withJSONObject:
body, options: .prettyPrinted) {
request.httpBody = data
}
return request
}()
self.server.execute(request: request, resolve: resolve)
}
}
}

This is a little complex, so let’s break it down. We are effectively building a fetch solution, into which we can inject an element of latency (such as a random time for testing against errors or for dispatching in a sequence). We will send across a url string, the method we want to add to the request and if we want to add body to our request. Plus we are adding an optional resolution of the type we created a moment ago.

After we have all these things, we simply build a URLRequest and pass it to our Server along with the resolution.

Reminder: We are not actually building a system for triggering the URLRequest such as through the URLSession. This Server is going to be an internal server, mocking out the interaction we would have should we be creating a real server and dispatching via the URLSession with a URLSessionTask.

Our final Client should look something like this:

// Final Client.swift
protocol Client {
var server: Server { get }
var queue: DispatchQueue { get }
}
extension Client {
func fetch(url string: String,
method: String = "GET",
body: Any? = nil,
latency: UInt32 = 0,
resolve: Resolution? = nil) {
guard let url = URL(string: string) else { return }
queue.asyncAfter(deadline: DispatchTime.now() +
DispatchTimeInterval.seconds(Int(latency))) {
let request: URLRequest = {
var request = URLRequest(url: url)
request.httpMethod = method
if let body = body,
let data = try? JSONSerialization.data(withJSONObject:
body, options: .prettyPrinted) {
request.httpBody = data
}
return request
}()
self.server.execute(request: request, resolve: resolve)
}
}
}

Server

OK! No we are ready to build out the big logic machine we are calling our server. If we look back at the code we wrote in the last article, we will see that we have a bunch of endpoints that will be accepted for transactions. So let’s get that set up.

class Server {
private typealias Elements = (components: URLComponents,
method: Method,
path: Path,
current: Int,
request: URLRequest)
private var chain = Chain()
private var index = 0
}
extension Server {
private enum Method: String {
case get = "GET"
case post = "POST"
}
private enum Path: String {
case value = "/value"
case chain = "/chain"
case creation = "/creation"
case exchange = "/exchange"
var description: String {
return String(rawValue.dropFirst())
}
}
}

We have defined our Server now as having two private variables, a Chain and an index. The first of these will be our immutable chain that can be replaced by a new version. We keep this private so that only the internal mechanism of this server can overwrite the chain instance. The second we will use for feedback and tracking the actual request index.

One more thing we have created is a type alias for a tuple that we will use in a little bit. This will simply allow us to create a method to encapsulate some heavy guard mechanisms.

We have also created two enums that describe the accepted methods and paths we will recognise when receiving requests from our Client.

We will also want to create a couple of Codable structs for determining the content of a request so we can build out the correct transaction.

extension Server {
private struct Creation: Codable {
let account: String
let value: Int?
}
private struct Exchange: Codable {
let to: String
let from: String
let value: Int
}
}

So we have two methods we accept. Let’s build out the simplest: GET

extension Server {
private func get(elements: Elements, resolve: Resolution?) {
switch elements.path {
case .value:
guard let account = elements
.components
.queryItems?
.last(where: {
$0.name == "account"
})?.value else {
resolve?(Error(transaction: elements.path.description,
reason: "No account supplied"),
elements.current)
return
}
guard let found = chain.find(account: account) else {
resolve?(Error(transaction: elements.path.description,
reason: "Invalid account: \(account)"),
elements.current)
return
}
resolve?(Chain(chain: [found]), elements.current)
return
case .chain:
resolve?(chain, elements.current)
return

default:
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method '\
(elements.method.rawValue)' for transaction \
(elements.path.description)"), elements.current)
return
}
}
}

This get method describes out business rules for when we create a get request. First of all we switch on the path. In our GET requests, we will only support the paths ‘/value’ and ‘/chain’ otherwise we will respond with an Error.

‘/chain’ is simple. We will just return the current chain.

‘/value’ is a bit more involved, and results in us ensuring the user have provided an account query and that the specific account exists. If it does, we send that value back.

You’ll notice that neither of these does anything to manipulate the Chain. They are read-only.

So if we want to edit the chain in anyway, we will use the POST mechanism:

extension Server {
private func post(elements: Elements, resolve: Resolution?) {
guard let body = elements.request.httpBody else {
resolve?(Error(transaction: elements.path.description,
reason: "No body supplied"),
elements.current)
return
}
switch elements.path {
case .creation:
guard let instance = try? JSONDecoder().decode(Creation.self,
from: body) else {
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method body for transaction
\(elements.path.description)"),
elements.current)
return
}
let response = self.chain
.transact(transaction: .creation(account:
instance.account,
value: instance.value ?? 0))
self.chain = (response as? Chain) ?? self.chain
resolve?(response, elements.current)
return
case .exchange:
guard let instance = try? JSONDecoder()
.decode(Exchange.self, from: body) else {
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method body for
transaction \(elements.path.description)"),
elements.current)
return
}
let response = self.chain
.transact(transaction: .exchange(from:
instance.from,
to: instance.to,
value: instance.value))
self.chain = (response as? Chain) ?? self.chain
resolve?(response, elements.current)
return
default:
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method
'\(elements.method.rawValue)' for transaction
\(elements.path.description)"), elements.current)
return
}
}
}

Our post method handles the next two of our supported URL paths. Before we get to that switch, however, we first ensure we have a valid body of data.

The creation path guards against the Creation Codable we created earlier and then passes this into the transaction and updates our chain the responds with the new chain.

The Exchange does the same, but with the Exchange Codable.

One we will now add in a quick utility method for guarding against invalid URLs.

extension Server {
private func guarding(request: URLRequest,
current: Int,
resolve: Resolution?) -> Elements? {
guard let url = request.url else {
resolve?(Error(transaction: "undefined",
reason: "Failed to parse valid URL"),
current)
return nil
}
guard let components = URLComponents(string: url.absoluteString)
else {
resolve?(Error(transaction: "undefined",
reason: "Failed to parse valid URL components"),
current)
return nil
}
guard let method = Method(rawValue: request.httpMethod
?? Method.get.rawValue) else {
resolve?(Error(transaction: "undefined",
reason: "Unsupported method"), current)
return nil
}
guard let path = Path(rawValue: components.path) else {
resolve?(Error(transaction: "undefined",
reason: "Unsupported url Path"), current)
return nil
}
return (components: components,
method: method,
path: path,
current: current,
request: request)
}
}

The method really just helps ensure we are sending valid error messages for failed URLRequests, with the resulting object being the Elements typealias we set up earlier.

And now finally, we can write our execution method:

extension Server {
func execute(request: URLRequest, resolve: Resolution? = nil) {
let current = self.index
self.index += 1
guard let elements = guarding(request: request,
current: current,
resolve: resolve) else { return }
switch elements.method {
case .get: get(elements: elements, resolve: resolve)
case .post: post(elements: elements, resolve: resolve)
}
}
}

And with this we have created the business logic we need to execute our request from the Client on our server. The final file should look something like this:

// Final Server.swift
class Server {
private typealias Elements = (components: URLComponents,
method: Method,
path: Path,
current: Int,
request: URLRequest)
private var chain = Chain()
private var index = 0
}
extension Server {
private enum Method: String {
case get = "GET"
case post = "POST"
}
private enum Path: String {
case value = "/value"
case chain = "/chain"
case creation = "/creation"
case exchange = "/exchange"
var description: String {
return String(rawValue.dropFirst())
}
}
}
extension Server {
private struct Creation: Codable {
let account: String
let value: Int?
}
private struct Exchange: Codable {
let to: String
let from: String
let value: Int
}
}
extension Server {
private func post(elements: Elements, resolve: Resolution?) {
guard let body = elements.request.httpBody else {
resolve?(Error(transaction: elements.path.description,
reason: "No body supplied"),
elements.current)
return
}
switch elements.path {
case .creation:
guard let instance = try? JSONDecoder().decode(Creation.self,
from: body) else {
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method body for transaction
\(elements.path.description)"),
elements.current)
return
}
let response = self.chain
.transact(transaction: .creation(account:
instance.account,
value: instance.value ?? 0))
self.chain = (response as? Chain) ?? self.chain
resolve?(response, elements.current)
return
case .exchange:
guard let instance = try? JSONDecoder()
.decode(Exchange.self, from: body) else {
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method body for
transaction \(elements.path.description)"),
elements.current)
return
}
let response = self.chain
.transact(transaction: .exchange(from:
instance.from,
to: instance.to,
value: instance.value))
self.chain = (response as? Chain) ?? self.chain
resolve?(response, elements.current)
return
default:
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method
'\(elements.method.rawValue)' for transaction
\(elements.path.description)"), elements.current)
return
}
}
}
extension Server {
private func post(elements: Elements, resolve: Resolution?) {
guard let body = elements.request.httpBody else {
resolve?(Error(transaction: elements.path.description,
reason: "No body supplied"),
elements.current)
return
}
switch elements.path {
case .creation:
guard let instance = try? JSONDecoder().decode(Creation.self,
from: body) else {
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method body for transaction
\(elements.path.description)"),
elements.current)
return
}
let response = self.chain
.transact(transaction: .creation(account:
instance.account,
value: instance.value ?? 0))
self.chain = (response as? Chain) ?? self.chain
resolve?(response, elements.current)
return
case .exchange:
guard let instance = try? JSONDecoder()
.decode(Exchange.self, from: body) else {
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method body for
transaction \(elements.path.description)"),
elements.current)
return
}
let response = self.chain
.transact(transaction: .exchange(from:
instance.from,
to: instance.to,
value: instance.value))
self.chain = (response as? Chain) ?? self.chain
resolve?(response, elements.current)
return
default:
resolve?(Error(transaction: elements.path.description,
reason: "Invalid method
'\(elements.method.rawValue)' for transaction
\(elements.path.description)"), elements.current)
return
}
}
}
extension Server {
private func guarding(request: URLRequest,
current: Int,
resolve: Resolution?) -> Elements? {
guard let url = request.url else {
resolve?(Error(transaction: "undefined",
reason: "Failed to parse valid URL"),
current)
return nil
}
guard let components = URLComponents(string: url.absoluteString)
else {
resolve?(Error(transaction: "undefined",
reason: "Failed to parse valid URL components"),
current)
return nil
}
guard let method = Method(rawValue: request.httpMethod
?? Method.get.rawValue) else {
resolve?(Error(transaction: "undefined",
reason: "Unsupported method"), current)
return nil
}
guard let path = Path(rawValue: components.path) else {
resolve?(Error(transaction: "undefined",
reason: "Unsupported url Path"), current)
return nil
}
return (components: components,
method: method,
path: path,
current: current,
request: request)
}
}
extension Server {
func execute(request: URLRequest, resolve: Resolution? = nil) {
let current = self.index
self.index += 1
guard let elements = guarding(request: request,
current: current,
resolve: resolve) else { return }
switch elements.method {
case .get: get(elements: elements, resolve: resolve)
case .post: post(elements: elements, resolve: resolve)
}
}
}

So this is ready for the next state. In the third and final part of this series, we are going to implement our own observer pattern for this Client-Server relationship and start to populate our preferred view.

About Paul:

Paul is a Cross-Platform Tech Lead with experience across iOS, Android and Web. In his spare time, apart from teaching people to code, he can be found writing out low-code, open-source solutions and advocating the automation of development work as well as its integration with machine learning and AI. You can check out his latest projects here:

https://www.rndm.com
https://www.github.com/rndm-com/

--

--