mimik Developer Documentation

Creating a Simple iOS Application that Uses an edge microservice

Objective

The objective of this tutorial is to demonstrate how to modify a small iOS application to use a microservice at the edge. The application code will use the mimik Client Library for iOS to access an edge microservice that generates a random number on demand.

Intended Readers

The intended readers of this document are software developers who want to familiarize themselves with mimik application development on iOS using the mimik Client Library.

In order to get the full benefit of this document, the intended readers should be familiar with the following:

  • The basics of the purpose and use of the mimik edgeEngine Runtime
  • The basics of using an Access Token to access and work with the edgeEngine Runtime
  • A basic understanding of programming in Swift
  • A basic understanding of iOS development using CocoaPods dependency management system

What You'll Be Doing

In this tutorial you are going to fix a bug in an existing iOS application. The application is a simple iOS application that is supposed to generate a random number when a button is tapped. However, there is a problem. The application does not generate a random number as expected, as shown in Figure 1, below.

buggy behavior
Figure 1: The demonstration application does not generate a random number as expected. This needs to be fixed.

Of course you could fix the code by just using the Swift Standard Library random(in:) method to generate the random number directly in the code. However, we're going to take another approach. We're going use the broken code as an opportunity to learn how to program iOS application code to bind to an edge microservice that provides a random number on demand.

In this tutorial you'll be doing the following tasks to fix the iOS application. The tasks below describe using an edge microservice to generate the random number that the application will display when a button is tapped.

These tasks are:

  • Clone the demonstration application code from GitHub
  • Configure the demonstration application to include the mimik Client Library for iOS as a CocoaPods artifact
  • Configure the demonstration application with the mimik Developer ID Token that will be used to generate the Access Token required to work with the edge microservice running under the edgeEngine Runtime.
  • Modify the demonstration application by adding code that will do the following:
    • Initialize the mimik Client Library and use it to start the edgeEngine Runtime.
    • Use the mimik Client Library with an existing Developer ID Token to generate and retrieve the Access Token that is required to deploy an edge microservice via the edgeEngine Runtime.
    • Deploy an edge microservice
    • Request a random number from the deployed edge microservice

In terms of doing the actual programming, after we've identified the problem area, we'll add the code to the files that will get the mimik Client Library for iOS from a private CocoaPods repository. Also, we'll alter the files that store the configuration information about the Developer ID Token. You'll copy the Developer ID Token from the mimik Developer Console.

Then, after the configuration is complete, we'll execute three phases of coding to do the work of actually getting the microservice up and running. The coding will take place in MainActivity.swift and ContentView.swift files.

In the first phase, we stub out the methods that relate to each programming step. Then, in the second phase, we'll add code to the methods in an isolated manner within the tutorial so the you can learn the reasoning and details about each function. Finally, we'll display the completed MainActivity.swift and ContentView.swift files that have all the code for all the methods. At that point you'll be able to run the fixed code on an iOS Device.

Also, be advised that the demonstration application source that you'll clone from GitHub has a branch named completed_code. This branch contains a version of the iOS application that has all the code you will be adding throughout the tutorial. You can checkout this branch on your local machine and run that code, should you experience difficulties running the code you've developed.

Technical Prerequisites

This tutorial has the following technical prerequisites:

  • A working Xcode 13 environment. You can download Xcode 13 from here.
  • An iOS device running iOS 15. The edgeEngine Runtime for iOS does not support simulated devices.

Working with the Demonstration Application and the mimik Client Library

The sections below describe the details of the steps required to fix the broken application code using the mimik Client Library for iOS. The mimik Client Library simplifies usage and provides straightforward interfaces to streamline edgeEngine startup, authorization, and microservice deployment at the edge.

Getting the Source Code

As mentioned above, you'll be modifying an existing iOS application to fix a bug in the code. The application you'll modify is an Xcode 13 project. The application code already has all the UI elements and initialization behavior needed to get the code up and running. The code is operational, but as mentioned, it's buggy.

The place to start is cloning the code from GitHub and loading it into Xcode.

Execute the following command to clone the demonstration code from the GitHub:

git clone https://github.com/mimikgit/random-number-generator-iOS.git

Adding the mimik Client Library for iOS to the Application Source Code

As mentioned above, the mimik Client Library for iOS MIMIKEdgeClient, which is a CocoaPods artifact, needs to be made available to the application source code. Also, you'll need its MIMIKEdgeClientIdentity service extension for authentication. Additionally, you'll need another CocoaPods artifact, the Alamofire library to facilitate HTTP requests to the edge microservice you'll be using once it's deployed.

You'll add these references in Podfile file at the project level.

Step 1: From command line run the following command to get to the Xcode project directory.

cd random-number-generator-iOS/Random-Number-Generator-Example/

Step 2: From command line run the following command (from inside the Xcode project directory).

pod init

Step 3: Start editing the Podfile file:

open Podfile

Step 4: Paste the following content into Podfile, replacing any existing content already there. Save.

1: platform :ios, '15.0'
2: source 'https://github.com/CocoaPods/Specs.git'
3: source 'https://github.com/mimikgit/cocoapod-edge-specs.git'
4:
5: target 'Random-Number-Generator-Example' do
6: # Comment the next line if you don't want to use dynamic frameworks
7: use_frameworks!
8:
9: # Pods for Random-Number-Generator-Example
10: pod 'MIMIKEdgeClient'
11: pod 'MIMIKEdgeClientIdentity'
12: pod 'Alamofire'
13: end

Step 5: From command line run the following command in your project directory.

pod install --repo-update

Step 6: Start editing the Developer-Token file:

open Developer-Token

Step 7: Go to the mimik Developer Portal and get Developer ID Token from your mimik Project. (See Figure 2 below)

NOTE: The Developer ID Token is labeled as ID Token in the mimik Developer Console UI.

Client ID and Developer ID Token
Figure 2: The Developer ID Token is stored in the mimik Developer Console

Copy the Developer ID Token and then paste it into Developer-Token file, replacing any existing content already there and Save.

WHERE

  • <DEVELOPER_TOKEN> is the Developer ID Token that was generated within the mimik Developer Console based on the Client ID. (You can read the details about the mimik Client ID and Developer ID Token in the Key Concepts page here.)

Step 7: From command line run the following command in your project directory.

open Random-Number-Generator-Example.xcworkspace

Figure 3 below shows the command line instructions described previously, from Steps 1-6 and also including this Step 7.

code in Xcode
Figure 3: Command line output example for Steps 1-6

Now that references and configurations have been set, it's time to get to the business of programming to the microservice at the edge.

Identifying the Bug

As mentioned at the beginning of this tutorial, our objective is to fix a bug that is preventing the demonstration application from displaying a random number when a button is tapped on the screen of the iOS device. The bad behavior we need to fix is in the MainActivity.swift file located in Xcode. The listing below shows the faulty code. The error is at Line 11.

1: import Foundation
2:
3: final class MainActivity: NSObject {
4:
5: override init() {
6: super.init()
7: }
8:
9: // Synchronous method that was supposed to return a randomly generated number
10: func generateRandomNumber() -> Int {
11: return 60
12: }
13: }

Figure 4 below displays a version of the the faulty code within the XCode IDE.

code in Xcode
Figure 4: Xcode project with CocoaPods

Notice that code returns a hard coded value of 60 in the generateRandomNumber() method at Line 10. This is the root cause. The work we'll do moving forward will remedy this issue. We're going to make the fix by using a microservice at the edge as we discussed earlier.

Implementing the Fix

In order to fix application code we'll be doing the following in the MainActivity.swift and ContentView.swift files.

  • Importing the mimik Client Library into the Project
  • Creating an instance of the mimik Client Library
  • Inserting the Method Placeholders
  • Modifying the inserted Method Placeholders

The mimik Client Library for iOS is represented in code as the MIMIKEdgeMobileClient class.

Importing the mimik Client Library into the Project

In order to be able to start calling the mimik Client Library for iOS and Alamofire networking library, we need to import these CocoaPods artifacts in the Xcode project. We do this by inserting the import statements for Alamofire, MIMIKEdgeClient and MIMIKEdgeClientIdentity in the MainActivity.swift file as shown below Lines 2-4:

1: import Foundation
2: import MIMIKEdgeClient
3: import MIMIKEdgeClientIdentity
4: import Alamofire
5:
6: final class MainActivity: NSObject {
7:
8: override init() {
9: super.init()
10: }
11:
12: // Synchronous method that was supposed to return a randomly generated number
13: func generateRandomNumber() -> Int {
14: return 60
15: }
16: }

Creating an instance of the mimik Client Library

In order to be able to start calling the mimik Client Library methods we need to create an instance of it. We do this by inserting the lazy variable lazy var mimikClientLibrary in the MainActivity.swift file as shown below at Lines 16-27. That the variable, mimikClientLibrary is a lazy variable means it will only be initialized when it is accessed for the first time. Also, the lazy variable will remain initialized for all subsequent calls.

1: import Foundation
2: import MIMIKEdgeClient
3: import MIMIKEdgeClientIdentity
4: import Alamofire
5:
6: final class MainActivity: NSObject {
7:
8: override init() {
9: super.init()
10: }
11:
12: // Synchronous method that was supposed to return a randomly generated number
13: func generateRandomNumber() -> Int {
14: return 60
15: }
16:
17: // A lazy instance variable of the mimik Client Library
18: // Will be initialized on first access only.
19: // Will remain initialized for all subsequent calls.
20: lazy var mimikClientLibrary: MIMIKEdgeClient = {
21: let library = MIMIKEdgeClient.init(license: nil)
22:
23: guard let checkedLibrary = library else {
24: fatalError()
25: }
26:
27: return checkedLibrary
28: }()
29: }

Inserting the Method Placeholders

We want to transform the application to start using the new edge microservice design paradigm. In order to do this we need to add a few placeholder methods and one new instance variable in the MainActivity.swift file as shown below at Lines 30-61.

The code is commented to describe the intention of the particular placeholder methods.

1: import Foundation
2: import MIMIKEdgeClient
3: import MIMIKEdgeClientIdentity
4: import Alamofire
5:
6: final class MainActivity: NSObject {
7:
8: override init() {
9: super.init()
10: }
11:
12: // Synchronous method that was supposed to return a randomly generated number
13: func generateRandomNumber() -> Int {
14: return 60
15: }
16:
17: // A lazy instance variable of the mimik Client Library
18: // Will be initialized on first access only.
19: // Will remain initialized for all subsequent calls.
20: lazy var mimikClientLibrary: MIMIKEdgeClient = {
21: let library = MIMIKEdgeClient.init(license: nil)
22:
23: guard let checkedLibrary = library else {
24: fatalError()
25: }
26:
27: return checkedLibrary
28: }()
29:
30: // Instance variable optional, a reference to the deployed edge microservice
31: var microservice: MIMIKMicroservice?
32:
33: // Asynchronous method starting the edgeEngine Runtime
34: // and returning a Bool indicating the result.
35: func startEdgeEngine() async -> Bool {
36: return false
37: }
38:
39: // Asynchronous method returning the ID Token that's necessary
40: // for edgeEngine Runtime authentication.
41: func getEdgeEngineIdToken() async -> String? {
42: return nil
43: }
44:
45: // Asynchronous method returning the Access Token that's necessary to work
46: // with the edge microservice running under the edgeEngine Runtime
47: func authorizeEdgeEngine(edgeEngineIdToken: String) async -> String? {
48: return nil
49: }
50:
51: // Asynchronous method deploying the edge microservice under the
52: // edgeEngine Runtime and returning an object representing it
53: // It requires Access Token as a parameter
54: func deployRandomNumberMicroservice(edgeEngineAccessToken: String) async -> MIMIKMicroservice? {
55: return nil
56: }
57:
58: // Asynchronous method returning a randomly generated number
59: func generateRandomNumber() async -> Int {
60: return 0
61: }
62: }

The sections that follow will show the code for each method we're programming. Also, we'll describe the reasoning behind each of the additions we're making to the code in the MainActivity.swift and ContentView.swift files.

Modifying the inserted Method Placeholders

In order to get the edge microservice installed and accessible we'll need to make the following changes in the MainActivity.swift and ContentView.swift files.

  • refactor the MainActivity.swift initialization method init()
  • modify the MainActivity.swift method startEdgeEngine()
  • modify the MainActivity.swift method getEdgeEngineIdToken()
  • modify the MainActivity.swift method authorizeEdgeEngine()
  • modify the MainActivity.swift method deployRandomNumberMicroservice()
  • modify the MainActivity.swift method generateRandomNumber()
  • modify the ContentView.swift body

Refactoring init()

We want to make the deployed microservice at the edge available for the ContentView.swift code to call. We're doing this so that the rendering view can get the randomly generated number when the user taps on its button. In order to implement the microservice that generates a random number we need an ordered sequence of actions to execute successfully during the MainActivity.swift initialization in init().

The init() method is synchronous. The four actions are asynchronous. We'll encapsulate the synchronous and asynchronous actions within a Task{} code wrapper as shown below in the code for the init() method starting at Line 5.

First, the edgeEngine Runtime needs to be started as shown below at Line 8. Second, we need to retrieve an edgeEngine ID Token from the edgeEngine Runtime as shown below at Line 14. Third, the edgeEngine Runtime needs to be authorized and the Access Token needs to be retrieved as shown below at Line 20. Fourth, the edge microservice needs to be deployed which is done at Line 26 below.

Additionally we store a reference to the deployed edge microservice as self.microservice at Line 31. This is so that we can refer to it later.

The code below is commented. Take a moment to review statements using the code comments as your guide. Then, if you're following along by doing live programming against the tutorial code you downloaded from GitHub, modify the init() method code in the MainActivity.swift file as shown below:

1: override init() {
2: super.init()
3:
4: // Async/await task wrapper
5: Task {
6:
7: // Check for the success of the startEdgeEngine asynchronous task. Fail fatally for an error.
8: guard await self.startEdgeEngine() else {
9: fatalError(#function)
10: }
11:
12: // Check for a success of the getEdgeEngineIdToken asynchronous task. Fail fatally for an error.
13: // Establish the edgeEngine ID Token as `let edgeEngineIdToken`
14: guard let edgeEngineIdToken = await self.getEdgeEngineIdToken() else {
15: fatalError(#function)
16: }
17:
18: // Check for a success of the startEdgeEngine asynchronous task. Fail fatally for an error.
19: // Establish the Access Token as `let edgeEngineAccessToken`
20: guard let edgeEngineAccessToken = await self.authorizeEdgeEngine(edgeEngineIdToken: edgeEngineIdToken) else {
21: fatalError(#function)
22: }
23:
24: // Check for a success of the deployRandomNumberMicroservice asynchronous task. Fail fatally for an error.
25: // Establish the deployed edge microservice reference as `let microservice`
26: guard let microservice = await self.deployRandomNumberMicroservice(edgeEngineAccessToken: edgeEngineAccessToken) else {
27: fatalError(#function)
28: }
29:
30: // Assign the deployed edge microservice reference to the `self.microservice` instance variable
31: self.microservice = microservice
32: }
33: }

Modifying startEdgeEngine()

We need to get the edgeEngine Runtime running. Once the edgeEngine Runtime is up and running it will automatically authorize itself to edge ecosystem. We'll get edgeEngine Runtime running by calling the startEdgeEngine() method of the mimik Client Library.

The startEdgeEngine() method of the mimik Client Library uses completion blocks. Also, the startEdgeEngine() method of MainActivity.swift uses async/await in order to control the flow of calls to asynchronous methods. This means a withCheckedContinuation wrapper needs to be used. The code for the function, startEdgeEngine() is shown below.

In terms of the details of the startEdgeEngine() function, first we call the self.mimikClientLibrary.startEdgeEngine() method of the mimik Client Library as shown below at line Line 9. Then we return the result value of the call with continuation.resume at Line 12.

Take a moment to review the statements in the code below using the comments as your guide. If you're following along using the code downloaded from GitHub, modify the startEdgeEngine() method code in the MainActivity.swift file as shown below.

1: // Asynchronous method starting the edgeEngine Runtime
2: // and returning a Bool indicating the result.
3: func startEdgeEngine() async -> Bool {
4:
5: // Closure wrapper for async/await
6: return await withCheckedContinuation { continuation in
7:
8: // Starting the edgeEngine Runtime with default startup parameters
9: self.mimikClientLibrary.startEdgeEngine(startupParameters: nil) { result in
10:
11: // Resuming the closure by returning the result value
12: continuation.resume(returning: (result))
13: }
14: }
15: }

Modifying getEdgeEngineIdToken()

Once we start the edgeEngine Runtime, as shown in the code example above, we also need to retrieve an edgeEngine ID Token from it. We do this because it is required for edgeEngine Runtime authentication. In order to accomplish this task we need to call the edgeEngineIdToken() method of the mimik Client Library.

The edgeEngineIdToken() method of the mimik Client Library also uses completion blocks. The getEdgeEngineIdToken() method of MainActivity.swift uses async/await too. As with the function startEdgeEngine() described previously, the getEdgeEngineIdToken() function will need a withCheckedContinuation wrapper.

In terms of the details of the getEdgeEngineIdToken() function, first we call the self.mimikClientLibrary.edgeEngineIdToken() method of the mimik Client Library as shown below at line Line 9. Then we return the result value of the call with continuation.resume at Line 12.

Take a moment to review the statements in the code below using the comments as your guide. If you're following along using the code downloaded from GitHub, modify the getEdgeEngineIdToken() method code in the MainActivity.swift file as shown below.

1: // Asynchronous method returning the ID Token that's necessary
2: // for edgeEngine Runtime authentication.
3: func getEdgeEngineIdToken() async -> String? {
4:
5: // Closure wrapper for async/await
6: return await withCheckedContinuation { continuation in
7:
8: // Starting the edgeEngine Runtime with default startup parameters
9: self.mimikClientLibrary.edgeEngineIdToken() { result in
10:
11: // Resuming the closure by returning the result value
12: continuation.resume(returning: (result))
13: }
14: }
15: }

Modifying authorizeEdgeEngine()

Once we have the edgeEngine ID Token, as shown in the code examples above, we also need to get the runtime authorized and an Access Token retrieved. We do this so that we can deploy the edge microservice. In order to accomplish this task we need to call the authorizeWithDeveloperIdToken() method of the mimik Client Library.

The authorizeWithDeveloperIdToken() method of the mimik Client Library also uses completion blocks. The authorizeEdgeEngine() method of MainActivity.swift uses async/await too. As with the function startEdgeEngine() described previously, the authorizeWithDeveloperIdToken() function will need a withCheckedContinuation wrapper.

The authorizeWithDeveloperIdToken() method of the mimik Client Library requires the Developer ID and edgeEngine ID Tokens in order to work. We saved the Developer ID to the Developer-Token file and the edgeEngine ID Token was passed as to our method as the edgeEngineIdToken parameter.

In order to implement authorization, first we need to find the application bundle reference to the Developer-Token file. The programming that identifies the application bundle reference starts at Line 9 below. We load the contents of the file into the let developerIdTokenFile variable Line 18. Additionally we remove any line breaks from the loaded string, as an extra precaution as shown at the end of Line 18.

At this point we are ready to make a call to the mimik Client Library's authorizeWithDeveloperIdToken() method. We send the developerIdToken and edgeEngineIdToken values as the parameters to function, as shown below at line Line 21.

Next we attempt to load the Access Token as let edgeEngineAccessToken shown at Line 24 from the authorization result. If successful, we return the Access Token at Line 32. If unsuccessful we return nil as shown at Line 27.

Take a moment to review the statements in the code below using the comments as your guide. If you're following along using the code downloaded from GitHub, modify the authorizeEdgeEngine() method code in the MainActivity.swift file as shown below.

1: // Asynchronous method returning the Access Token that's necessary to work
2: // with the edge microservice running under the edgeEngine Runtime
3: func authorizeEdgeEngine(edgeEngineIdToken: String) async -> String? {
4:
5: // Closure wrapper for async/await
6: return await withCheckedContinuation { continuation in
7:
8: // Establishing application bundle reference to the Developer-Token file
9: guard let developerIdTokenFile = Bundle.main.path(forResource: "Developer-Token", ofType: nil) else {
10:
11: // Resuming the closure by returning a nil. This is a failed scenario.
12: continuation.resume(returning: nil)
13: return
14: }
15:
16: do {
17: // Loading the content of Developer-Token file as a String
18: let developerIdToken = try String(contentsOfFile: developerIdTokenFile).replacingOccurrences(of: "\n", with: "")
19:
20: // Authorizing edgeEngine Runtime. Passing the content of Developer-Token file and edgeEngine ID Token
21: self.mimikClientLibrary.authorizeWithDeveloperIdToken(developerIdToken: developerIdToken, edgeEngineIdToken: edgeEngineIdToken) { result in
22:
23: // Retrieving the Access Token from the result of the authorization call
24: guard let edgeEngineAccessToken = result.tokens?.accessToken else {
25:
26: // Resuming the closure by returning a nil. This is a failed scenario.
27: continuation.resume(returning: nil)
28: return
29: }
30:
31: // Resuming the closure by returning the Access Token
32: continuation.resume(returning: edgeEngineAccessToken)
33: }
34:
35: } catch {
36: // Resuming the closure by returning a nil. This is a failed scenario.
37: continuation.resume(returning: nil)
38: return
39: }
40: }
41: }

Modifying deployRandomNumberMicroservice()

At this point we're going to deploy the edge microservice to the edgeEngine Runtime. For this we need to call the deployRandomNumberMicroservice() method of the mimik Client Library first.

deployMicroservice() method of the mimik Client Library is using completion blocks. deployRandomNumberMicroservice() method of MainActivity.swift is using async/await. We'll use a withCheckedContinuation wrapper as we've done previously. This current intance of withCheckedContinuation is shown in the code below at Line 7.

The deployMicroservice() method of the mimik Client Library requires the Access Token. The Access Token will be passed through to our method as the edgeEngineAccessToken parameter as shown at Line 4 below.

The deployMicroservice() method of the mimik Client Library requires a file path to the microservice at the edge that's being deployed. In our case the microservice is represented by the randomnumber_v1.tar file. This file resides in the application bundle. We get a reference to the file at Line 10.

The deployMicroservice() method of the mimik Client Library requires a MIMIKMicroserviceDeploymentConfig configuration object. We've taken the liberty of configuring it using hardcoded values at shown at Line 18. This is done to make the code easier to understand. In a production setting you'll probably want to put all the hard coded values in a configuration file and naming the values accordingly.

Now we have the deployment configuration object setup and the path to the edge microservice file established. We also have the Access Token. Next we call the deployMicroservice() method of the mimik Client Library, passing the edgeEngineAccessToken, config and imageTarPath objects as the parameters as shown below at Line 21.

We resume the closure by returning the reference to the deployed edge microservice as shown at Line 24.

Take a moment to review the statements in the code below using the comments as your guide. If you're following along using the code downloaded from GitHub, modify the deployRandomNumberMicroservice() method code in the MainActivity.swift file as shown below.

1: // Asynchronous method deploying the edge microservice under the
2: // edgeEngine Runtime and returning an object representing it
3: // It requires Access Token as a parameter
4: func deployRandomNumberMicroservice(edgeEngineAccessToken: String) async -> MIMIKMicroservice? {
5:
6: // Closure wrapper for async/await
7: return await withCheckedContinuation { continuation in
8:
9: // Establishing application bundle reference to the randomnumber_v1.tar file
10: guard let imageTarPath = Bundle.main.path(forResource: "randomnumber_v1", ofType: "tar") else {
11:
12: // Resuming the closure by returning a nil. This is a failed scenario.
13: continuation.resume(returning: nil)
14: return
15: }
16:
17: // Setting up the deployment configuration object with hardcoded values for simplicity.
18: let config = MIMIKMicroserviceDeploymentConfig.init(imageName: "randomnumber-v1", containerName: "randomnumber-v1", baseApiPath: "/randomnumber/v1", envVariables: [:])
19:
20: // Deploying the Random Number edge microservice. Passing the Access Token, deployment configuration object and a path to the randomnumber_v1.tar file
21: self.mimikClientLibrary.deployMicroservice(edgeEngineAccessToken: edgeEngineAccessToken, config: config, imageTarPath: imageTarPath) { microservice in
22:
23: // Resuming the closure by returning reference to the deployed edge microservice
24: continuation.resume(returning: microservice)
25: }
26: }
27: }

Modifying generateRandomNumber()

Previously the buggy function, generateRandomNumber() just returned a hard-coded value. In order to implement a fix, we want the generateRandomNumber() method to return a random number. For this we need to make an HTTP call to the randomNumber endpoint on the deployed microservice at the edge and return the random value from the HTTP response. We'll use the Alamofire networking library's request() method to make this call.

The request() method of the Alamofire is using completion blocks. The generateRandomNumber() method of MainActivity.swift is using async/await as we've done before. Also, as with previous examples, we'll use a withCheckedContinuation wrapper.

In order to establish the full URL of the randomNumber endpoint on the deployed edge microservice we need to do a bit of discovery. First we establish the base API path of the deployed edge microservice as let microserviceBaseApiPath as shown in the code below at Line 5. Then we define the URL of the edgeEngine Runtime as the variable let edgeEngineServiceLink by calling the self.mimikClientLibrary.edgeEngineServiceLink() method of the mimik Client Library as shown below at Line 12. Next we define the edge microservice endpoint as the variable let microserviceEndpoint as shown at Line 15. Finally we define the full URL for the randomNumber endpoint as the variable let microserviceFullUrlString shown below Line 18.

Next we turn it into a URL object by declaring let microserviceFullUrl at Line 21 below. Then we establish a URLRequest object by declaring let urlRequest at Line 31 below.

With all the objects now established we can use the request() method of the Alamofirelibrary to call the endpoint on the deployed edge microservice Line 34.

Next we look at the response result in the responseJSON completion block at Line 34. Using a switch statement we determine whether the request was successful, as determined by the Alamofire library at Line 38. Then we attempt to establish the return value by assigning the return to the variable let intValue as shown at Line 41. If request made to the microservice at the edge was successful, we return the random number value with a continuation.resume statement as shown at Line 49 below. If there was a failure with either the result value as shown at Line 44 or at the Alamofire network library as shown at Line 54 we return a 0 with continuation.resume at Lines 44 or 54.

Take a moment to review the statements in the code below using the comments as your guide. If you're following along using the code downloaded from GitHub, modify the generateRandomNumber() method code in the MainActivity.swift file as shown below.

1: // A new asynchronous method returning a randomly generated number from the deployed edge microservice
2: func generateRandomNumber() async -> Int {
3:
4: // Getting a reference to the deployed edge microservice's base API path
5: guard let microserviceBaseApiPath = self.microservice?.baseApiPath() else {
6:
7: // Returning a zero. This is a failed scenario.
8: return 0
9: }
10:
11: // Getting a url to the edgeEngine Runtime instance. This includes a self-managed service port
12: let edgeEngineServiceLink = self.mimikClientLibrary.edgeEngineServiceLink()
13:
14: // Defining the Random Number endpoint on the deployed edge microservice
15: let microserviceEndpoint = "/randomNumber"
16:
17: // Combining the edgeEngine Runtime instance url with the deployed edge microservice's base API and the Random Number endpoint
18: let microserviceFullUrlString = edgeEngineServiceLink + microserviceBaseApiPath + microserviceEndpoint
19:
20: // Creating a URL object from the combined url string
21: guard let microserviceFullUrl = URL.init(string: microserviceFullUrlString) else {
22:
23: // Returning a zero. This is a failed scenario.
24: return 0
25: }
26:
27: // Closure wrapper for async/await
28: return await withCheckedContinuation { continuation in
29:
30: // Creating a URLRequest object from the URL object
31: let urlRequest = URLRequest.init(url: microserviceFullUrl)
32:
33: // using Alamofire networking library to make the HTTP call, parse the response and do basis error checking
34: AF.request(urlRequest).responseJSON { response in
35:
36: // Determining the success or failure of the HTTP call
37: switch response.result {
38: case .success(let data):
39:
40: // Attempting the extract the random number value as an Int
41: guard let intValue = data as? Int else {
42:
43: // Resuming the closure by returning a 0. This is a failed scenario.
44: continuation.resume(returning: 0)
45: return
46: }
47:
48: // Resuming the closure by returning random number value
49: continuation.resume(returning: intValue)
50:
51: case .failure(_):
52:
53: // Resuming the closure by returning a nil. This is a failed scenario.
54: continuation.resume(returning: 0)
55: }
56: }
57: }
58: }

We now have fully operational code in the MainActivity.swift file. Next we'll change code specific to this application's view in the ContentView.swift file.

Refactoring ContentView.swift

What we need to do now is to make a change where the view rendering code calls the deployed microservice at the edge; more specifically where the user taps on the button to get the randomly generated number.

The code ContentView.swift runs synchronously. The generateRandomNumber() instance method of MainActivity uses async/await. This means a Task{} code wrapper needs to be used.

We want to start using the new and improved generateRandomNumber() MainActivity asynchronous method. To do this we first add a Task{} code wrapper in Line 22. Then we switch to the new asynchronous generateRandomNumber() method at Line 23. Now, when a user taps the button on screen of an iOS device, a random number will appear.

Take a moment to review the statements in the code below using the comments as your guide. If you're following along using the code downloaded from GitHub, modify the code in the ContentView.swift file as shown below.

1: import SwiftUI
2:
3: struct ContentView: View {
4:
5: // View's random number value instance variable
6: @State private var randomNumber: Int = 0
7: // View's MainActivity class instance variable
8: @State private var mainActivity = MainActivity()
9:
10: // View's body
11: var body: some View {
12: VStack(alignment: .center, spacing: 30) {
13:
14: // View's title text
15: Text("mimik Random Number Generator")
16: .font(.title2)
17:
18: // View's button with an action closure
19: Button.init("GET RANDOM NUMBER") {
20:
21: // Calling the fixed, new asynchronous method in a await/async wrapper
22: Task {
23: randomNumber = await mainActivity.generateRandomNumber()
24: }
25:
26: }.tint(Color.blue)
27:
28: // Showing the current random number value on the screen
29: Text("Got \(randomNumber)")
30: .font(.body)
31: }
32: }
33: }

We now have fully operational code in the ContentView.swift file as well. But, there is one last topic to cover: how to reset the edgeEngine Runtime, which we'll cover next.

Resetting the edgeEngine Runtime

At this point we've added all the code necessary to fix the original bug in the demonstration application. But, there is still an outstanding risk. We really haven't programmed a way to reset the state of the underlying edgeEngine Runtime that is supporting the microservice. Let's cover this topic now, briefly.

Resetting the edgeEngine Runtime will remove all authorization data and reset the authorization status. Resetting the edgeEngine Runtime also clears any data stored and removes the deployed microservices. Finally, resetting stops the edgeEngine Runtime. The edgeEngine Runtime will return to an unauthorized, pristine state.

If you want to add the reset behavior to the demonstration application, you can use the following code to accomplish this task.

1: // Stop edgeEngine and clear authorization, state, and stored data
2: self.mimikClientLibrary.unauthorizeClientLibrarySynchronously()

You'll need to figure out the best place to put the code. One way to implement the behavior is to add another button to the application's screen and put the reset behavior in that button's tap handler.

No matter how you decide to implement reset behavior, the important thing to remember is that the mimik Client Library for iOS provides the capability to reset the state of the underlying edgeEngine Runtime and that reset behavior is executed using the unauthorizeClientLibrarySynchronously() method.

Viewing the Completed Code for MainActivity.java

The sections above showed you the details that go with getting the configuration settings, import statements, class variables and methods in place in order to implement an edge microservice. The microservice we added fixes the bug that was in the demonstration application. The listing below shows the entirety of the MainActivity.swift and ContentView.swift files with all the code added to the refactored and placeholders methods.

1: import Foundation
2: import MIMIKEdgeClient
3: import MIMIKEdgeClientIdentity
4: import Alamofire
5:
6: final class MainActivity: NSObject {
7:
8: override init() {
9: super.init()
10:
11: // Async/await task wrapper
12: Task {
13:
14: // Check for the success of the startEdgeEngine asynchronous task. Fail fatally for an error.
15: guard await self.startEdgeEngine() else {
16: fatalError(#function)
17: }
18:
19: // Check for a success of the getEdgeEngineIdToken asynchronous task. Fail fatally for an error.
20: // Establish the edgeEngine ID Token as `let edgeEngineIdToken`
21: guard let edgeEngineIdToken = await self.getEdgeEngineIdToken() else {
22: fatalError(#function)
23: }
24:
25: // Check for a success of the startEdgeEngine asynchronous task. Fail fatally for an error.
26: // Establish the Access Token as `let edgeEngineAccessToken`
27: guard let edgeEngineAccessToken = await self.authorizeEdgeEngine(edgeEngineIdToken: edgeEngineIdToken) else {
28: fatalError(#function)
29: }
30:
31: // Check for a success of the startEdgeEngine asynchronous task. Fail fatally for an error.
32: // Establish the deployed edge microservice reference as `let microservice`
33: guard let microservice = await self.deployRandomNumberMicroservice(edgeEngineAccessToken: edgeEngineAccessToken) else {
34: fatalError(#function)
35: }
36:
37: // Assign the deployed edge microservice reference to the `self.microservice` instance variable
38: self.microservice = microservice
39: }
40: }
41:
42: // Synchronous method that was supposed to return a randomly generated number
43: func generateRandomNumber() -> Int {
44: return 60
45: }
46:
47: // A lazy instance variable of the mimik Client Library
48: // Will be initialized on first access only.
49: // Will remain initialized for all subsequent calls.
50: lazy var mimikClientLibrary: MIMIKEdgeClient = {
51: let library = MIMIKEdgeClient.init(license: nil)
52:
53: guard let checkedLibrary = library else {
54: fatalError()
55: }
56:
57: return checkedLibrary
58: }()
59:
60: // Instance variable optional, a reference to the deployed edge microservice
61: var microservice: MIMIKMicroservice?
62:
63: // Asynchronous method starting the edgeEngine Runtime
64: // and returning a Bool indicating the result.
65: func startEdgeEngine() async -> Bool {
66:
67: // Closure wrapper for async/await
68: return await withCheckedContinuation { continuation in
69:
70: // Starting the edgeEngine Runtime with default startup parameters
71: self.mimikClientLibrary.startEdgeEngine(startupParameters: nil) { result in
72:
73: // Resuming the closure by returning the result value
74: continuation.resume(returning: (result))
75: }
76: }
77: }
78:
79: // Asynchronous method returning the ID Token that's necessary
80: // for edgeEngine Runtime authentication.
81: func getEdgeEngineIdToken() async -> String? {
82:
83: // Closure wrapper for async/await
84: return await withCheckedContinuation { continuation in
85:
86: // Starting the edgeEngine Runtime with default startup parameters
87: self.mimikClientLibrary.edgeEngineIdToken() { result in
88:
89: // Resuming the closure by returning the result value
90: continuation.resume(returning: (result))
91: }
92: }
93: }
94:
95: // Asynchronous method returning the Access Token that's necessary to work
96: // with the edge microservice running under the edgeEngine Runtime
97: func authorizeEdgeEngine(edgeEngineIdToken: String) async -> String? {
98:
99: // Closure wrapper for async/await
100: return await withCheckedContinuation { continuation in
101:
102: // Establishing application bundle reference to the Developer-Token file
103: guard let developerIdTokenFile = Bundle.main.path(forResource: "Developer-Token", ofType: nil) else {
104:
105: // Resuming the closure by returning a nil. This is a failed scenario.
106: continuation.resume(returning: nil)
107: return
108: }
109:
110: do {
111: // Loading the content of Developer-Token file as a String
112: let developerIdToken = try String(contentsOfFile: developerIdTokenFile).replacingOccurrences(of: "\n", with: "")
113:
114: // Authorizing edgeEngine Runtime. Passing the content of Developer-Token file and edgeEngine ID Token
115: self.mimikClientLibrary.authorizeWithDeveloperIdToken(developerIdToken: developerIdToken, edgeEngineIdToken: edgeEngineIdToken) { result in
116:
117: // Retrieving the Access Token from the result of the authorization call
118: guard let edgeEngineAccessToken = result.tokens?.accessToken else {
119:
120: // Resuming the closure by returning a nil. This is a failed scenario.
121: continuation.resume(returning: nil)
122: return
123: }
124:
125: // Resuming the closure by returning the Access Token
126: continuation.resume(returning: edgeEngineAccessToken)
127: }
128:
129: } catch {
130: // Resuming the closure by returning a nil. This is a failed scenario.
131: continuation.resume(returning: nil)
132: return
133: }
134: }
135: }
136:
137: // Asynchronous method deploying the edge microservice under the
138: // edgeEngine Runtime and returning an object representing it
139: // It requires Access Token as a parameter
140: func deployRandomNumberMicroservice(edgeEngineAccessToken: String) async -> MIMIKMicroservice? {
141:
142: // Closure wrapper for async/await
143: return await withCheckedContinuation { continuation in
144:
145: // Establishing application bundle reference to the randomnumber_v1.tar file
146: guard let imageTarPath = Bundle.main.path(forResource: "randomnumber_v1", ofType: "tar") else {
147:
148: // Resuming the closure by returning a nil. This is a failed scenario.
149: continuation.resume(returning: nil)
150: return
151: }
152:
153: // Setting up the deployment configuration object with hardcoded values for simplicity.
154: let config = MIMIKMicroserviceDeploymentConfig.init(imageName: "randomnumber-v1", containerName: "randomnumber-v1", baseApiPath: "/randomnumber/v1", envVariables: [:])
155:
156: // Deploying the Random Number edge microservice. Passing the Access Token, deployment configuration object and a path to the randomnumber_v1.tar file
157: self.mimikClientLibrary.deployMicroservice(edgeEngineAccessToken: edgeEngineAccessToken, config: config, imageTarPath: imageTarPath) { microservice in
158:
159: // Resuming the closure by returning reference to the deployed edge microservice
160: continuation.resume(returning: microservice)
161: }
162: }
163: }
164:
165: // A new asynchronous method returning a randomly generated number from the deployed edge microservice
166: func generateRandomNumber() async -> Int {
167:
168: // Getting a reference to the deployed edge microservice's base API path
169: guard let microserviceBaseApiPath = self.microservice?.baseApiPath() else {
170:
171: // Returning a zero. This is a failed scenario.
172: return 0
173: }
174:
175: // Getting a url to the edgeEngine Runtime instance. This includes a self-managed service port
176: let edgeEngineServiceLink = self.mimikClientLibrary.edgeEngineServiceLink()
177:
178: // Defining the Random Number endpoint on the deployed edge microservice
179: let microserviceEndpoint = "/randomNumber"
180:
181: // Combining the edgeEngine Runtime instance url with the deployed edge microservice's base API and the Random Number endpoint
182: let microserviceFullUrlString = edgeEngineServiceLink + microserviceBaseApiPath + microserviceEndpoint
183:
184: // Creating a URL object from the combined url string
185: guard let microserviceFullUrl = URL.init(string: microserviceFullUrlString) else {
186:
187: // Returning a zero. This is a failed scenario.
188: return 0
189: }
190:
191: // Closure wrapper for async/await
192: return await withCheckedContinuation { continuation in
193:
194: // Creating a URLRequest object from the URL object
195: let urlRequest = URLRequest.init(url: microserviceFullUrl)
196:
197: // using Alamofire networking library to make the HTTP call, parse the response and do basis error checking
198: AF.request(urlRequest).responseJSON { response in
199:
200: // Determining the success or failure of the HTTP call
201: switch response.result {
202: case .success(let data):
203:
204: // Attempting the extract the random number value as an Int
205: guard let intValue = data as? Int else {
206:
207: // Resuming the closure by returning a 0. This is a failed scenario.
208: continuation.resume(returning: 0)
209: return
210: }
211:
212: // Resuming the closure by returning random number value
213: continuation.resume(returning: intValue)
214:
215: case .failure(_):
216:
217: // Resuming the closure by returning a nil. This is a failed scenario.
218: continuation.resume(returning: 0)
219: }
220: }
221: }
222: }
223: }
1: import SwiftUI
2:
3: struct ContentView: View {
4:
5: // View's random number value instance variable
6: @State private var randomNumber: Int = 0
7: // View's MainActivity class instance variable
8: @State private var mainActivity = MainActivity()
9:
10: // View's body
11: var body: some View {
12: VStack(alignment: .center, spacing: 30) {
13:
14: // View's title text
15: Text("mimik Random Number Generator")
16: .font(.title2)
17:
18: // View's button with an action closure
19: Button.init("GET RANDOM NUMBER") {
20:
21: // Calling the fixed, new asynchronous method in a await/async wrapper
22: Task {
23: randomNumber = await mainActivity.generateRandomNumber()
24: }
25:
26: }.tint(Color.blue)
27:
28: // Showing the current random number value on the screen
29: Text("Got \(randomNumber)")
30: .font(.body)
31: }
32: }
33: }

If you've followed along by inserting and adding code as instructed throughout this tutorial, running the code is a matter of using the capabilities provided by Xcode to run the code on the attached iOS device.

If for some reason you can't get your code up and running, you can use the working version of this code that ships in the cloned repository. It is in a branch named completed_code. You can run that code by cloning the repository for this demonstration project to a different location in your local file system. Then once the code is downloaded via cloning, go to the source code's working directory, open a terminal window in that directory and execute the following command:

git checkout completed_code

Once you've checked out completed_code, run pod instal in that Xcode project directory. Additionally don't forget to save your Developer ID Token to the Developer-Token there as well. Then open the project in Xcode. Then, build and run the code on the attached iOS device.

after-deployment
Figure 5: The demonstration application with the working randomization code from the microservice at the edge

Congratulations! You have completed the demonstration application that uses an microservice at the edge to provide behavior to fix the operational bug in application. Remember, the application was unable to display a random number each time the Get Random Number button was tapped. Now by binding the button tap handler to make a call to the edge microservice, it does.

Review

In this document you learned the following:

  • How to configure the demonstration application to get the mimik Client Library for iOS from a private CocoaPods repository
  • How to configure the demonstration application with Developer ID Token credentials
  • How to instantiate and start the mimik edgeEngine runtime using the mimik Client Library for iOS
  • How to use a Developer ID Token to authorize an edgeEngine runtime and generate an Access Token
  • How to use a generated Access Token to deploy a microservice at the edge
  • How to make a request to a microservice at the edge to retrieve a random value
  • How to reset edgeEngine to return it to a like-new state
© 2021 mimik Technology Inc. All Rights Reserved