Writing the ProcessingController

The ProcessingController is an internal-only controller. It will be used for performing actions with orders that the user has no input into.

Follow these steps to create our controller:

  1. Open Sources/App/Controllers, create a ProcessingController.swift file, and enter the following content into the file:
import Fluent
import Vapor

final class ProcessingController {
}

This code is only the container for the class. We will need the following three functions:

  • getOrders: This returns a list of orders based on a status.
  • processOrderInformation: This checks the prices of an order according to the PMS.
  • getProductInformation: This returns the prices from the PMS.
  1. Create the three function templates:
func getOrders(status: Int = 0, on app: Application) throws ->
EventLoopFuture<[Order]> {
}

func processOrderInformation(_ order: Order, on app: Application) ->
EventLoopFuture<Bool> {
}

func getProductInformation(productIds: [Int], client: Client) ->
EventLoopFuture<[Product]> {
}

So far all these functions are not yet filled; we will work on that shortly. Notice that we are passing on Application to two of the functions. The reason is that when we are running the functions not from a user request, we typically don't have a request instance available. Application provides us what we need, though.

Next, we need to make sure we are getting the PSM URL so that we know where to get the prices from.

  1. Enter the following just below the first { curly brace:
let productServiceUrl:String

init(_ productServiceUrl: String) {
self.productServiceUrl = productServiceUrl
}

When initializing this controller, we are expecting the PSM URL. We will call this URL later to retrieve the product information.

Next, let's work on actually returning the information.

  1. Fill out the getProductInformation function as follows:
func getProductInformation(productIds: [Int], client: Client) ->
EventLoopFuture<[Product]> {
return client.get(URI(string: self.productServiceUrl),
headers: ["Content-Type": "application/json"]).
flatMapThrowing { (response:ClientResponse) in
return try response.content.decode([Product].self)
}
}

You can see that this part is surprisingly easy and doesn't need all that much additional logic. We request from a previously defined URL, and parse the information into our data model.

  1. Use the just created function to process an order as follows:
func processOrderInformation(_ order: Order, on app: Application) -> EventLoopFuture<Bool> {
let productIds:[Int] = order.items.map { $0.productId }

let expectedTotal = order.totalAmount

return self.getProductInformation(productIds: productIds,
client: app.make(Client.self)).flatMap {
(products:[Product]) -> EventLoopFuture<Bool> in
var total = 0
for item in order.items {
for product in products {
if product.id == item.productId {
total = total + product.unitPrice *
item.quantity
}
}
}
if total != expectedTotal {
return app.make(EventLoop.self).makeFailedFuture(
OrderError.totalsNotMatching)
}
order.totalAmount = total
order.status = 1
return order.save(on: app.databases.default()).
transform(to: true)
}
}

In the first line, we create an array of product ids for us (productIds). This array we can then pass onto the getProductInformation function. The result of that function is an EventLoopFuture that contains the products. We loop through the products to then calculate our own total price.

After the loop, we then compare to see if the total we calculated (total) matches the expected total (expectedTotal) from the user, if not we return a failed future. Otherwise, everything seems okay, and we assign a new status to the order and save it.

  1. Now all that is left is to create our function to get orders:
func getOrders(status: Int = 0, on app: Application) throws -> EventLoopFuture<[Order]> {
return Order.query(on: app.databases.default()).
filter(Order.$status == status).all()
}

This code just returns all orders to us with a filter for the status. We leave the status as a parameter because we want to provide a way for us to use these functions in other scenarios as well.

Finally, we can now work on the part of the app that runs the controller. To schedule the task, we can use some functions from SwiftNIO.

Using the functions from SwiftNIO here is excellent for what we are doing. Depending on what you need, you may want to use more sophisticated job queue systems that include recovery and retries.
  1. Fill out the boot function in boot.swift like the following:
import Vapor
import NIO

private var verifyOrdersTask: RepeatedTask? = nil

public func boot(_ app: Application) throws {

let url = Environment.get("PRODUCT_SERVICE_URL") ??
"http://localhost:8080/v1/products"

let processingController = ProcessingController(url)

verifyOrdersTask = app.make(EventLoop.self).
scheduleRepeatedAsyncTask(
initialDelay: .seconds(0),
delay: .seconds(60)
) { [weak app] task -> EventLoopFuture<Void> in
let app = app!

var returnFuture:EventLoopFuture<Void>

do {
returnFuture = try processingController.
getOrders(status: 0, on: app).flatMap {
orders -> EventLoopFuture<Void> in
var processingFutures:[EventLoopFuture<Void>] = []

for order in orders {
processingFutures.append(processingController.
processOrderInformation(order, on: app).
transform(to: ()))
}
return processingFutures.flatten(on: app.make(
EventLoop.self))
}
}
catch let error {
returnFuture = app.make(EventLoop.self).
makeFailedFuture(error)
}

return returnFuture
}
}

In this code, we create an instance of ProcessingController (the only one in the service), and then we use SwiftNIO to start a repeating task. In this task, we simply ask for all open orders and then process them. We do that by calling the corresponding functions of our controller.

Note how we are pulling the PSM URL from the environment. We then merely call the functions from our ProcessingController.

So, this is now finished. We have now written the controllers and models as well as a routine to process our orders as they come in. Let's next look at how to extend this service.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.214.146