Part 2: Apache OpenWhisk and IBM Cloud You’d Think I’d Worry About Baking Bread… Recall from Part 1 of this post — — that I have decided to form a startup, , an online bakery specializing in gourmet bread. My CLO ( ) is willing to go along with my prioritizing cloud infrastructure over actually baking product. But she’s concerned that we haven’t fully explored our options. In this article, I’ll re-write the code from Part 1 to run under . : Swift and AWS Lambda Serverless Computing with Swift It’s The Yeast I Can Do Chief Leavening Officer Apache OpenWhisk OpenWhisk is an open source platform for serverless computing. You can host it on your own servers, with or without containers (such as Docker). You can even run it on AWS. For this article, I will use , the cloud services infrastructure formerly known as . IBM Cloud Bluemix (If you’re following along at home, you’ll need an IBM account. (You can sign up for a free trial here ) After registering for an account, install the CLI and its Cloud Functions plugin; follow the instructions here and here . In order to avoid cryptic error messages later on, make sure to create an organization and space in the same region.) OpenWhisk provides out-of-the-box, direct support for several languages including JavaScript, Python, Java, Go, and Swift. OpenWhisk also supports other languages via Docker containers; you can write actions in Lua, Forth, Rust, or pretty much any language, even Bash scripts. Not only does OpenWhisk let you program in Swift, but the infrastructure supports directly. In the AWS code from Part 1, I had to read the data from standard input and use to deserialize it. With OpenWhisk, however, the main function is handed a deserialized object. Codable JSONDecoder Since Swift support is baked in, compiling the code be easier than what we had to do with AWS. Let’s see if that holds true. should An Example In , I created a serverless function that would receive a list of items the customer ordered and return a receipt. Although the code for the new version will be very similar to the original, I’ll start it from scratch ( ) for simplicity. Part 1 baking pun intended To begin, I’ll do something wrong, but easy. First, I create a directory for my code and switch to it: mkdir yeast cd yeast Then, I combine, , , and into one file, call it . These are unchanged from the in Part 1. Item.swift Order.swift Receipt.swift Yeast.swift gist curl -o Item.swift https://git.io/fh9Kg curl -o Order.swift https://git.io/fh9K2 curl -o Receipt.swift https://git.io/fh9Ka cat Item.swift Order.swift Receipt.swift > Yeast.swift I need to create the main function at the bottom of the file, . Here’s the difference from the AWS version: the OpenWhisk infrastructure does the input decoding, but the action’s main function must have the following signature: Yeast.swift main(input: Codable, completion: (Codable?, Error?) -> Void) -> Void ( ) Actually, there are a couple of alternative signatures that are not currently documented. You can read the OpenWhisk source code for details. I edit adding a main function along with a couple of helper structs: Yeast.swift public struct Output: Codable {let receipt: String} public struct Input: Codable {let items: [Item]} func main(param: Input, completion: (Output?, Error?) -> Void) -> Void {let receipt = Receipt(with: param.items)completion(Output(receipt: “\(receipt)”), nil)} and are necessary because strings aren’t allowed as top-level JSON objects, so I wrap the strings in simple structs. Note — Input Output Adding in OpenWhisk… Finally, I create an OpenWhisk action ( ) using IBM’s command line tool. The incantation is as follows: equivalent to an AWS Lambda function ibmcloud fn action create yeast Yeast.swift — kind swift:4.1 Which results in the message “ok: created action yeast” And now to test it, I create a parameter file, named , with contents: params.json {“items”: [{“amount”: 3, “style”: “rye”},{“amount”: 4, “style”: “naan”}]} And invoke the action like so: ibmcloud fn action invoke -r yeast — param-file params.json I get the following JSON printed to my console: {“receipt”: “Receipt for Order on 2018–08–02 19:44:37 +0000\n — — — — -\n3 RYE @ 0.62 = 1.86\n4 NAAN @ 0.87 = 3.48\n — — — — -\nTotal: 5.34\n\n”} The switch performs a blocking invocation and limits the response to just the output of the action. If, instead, I had typed the following: -r ibmcloud fn action invoke -b yeast — param-file params.json I also get a blocking invocation, but in addition to the action’s output, I get a lot of metadata such as start and end timestamps and duration. Invoking the action without either the or switches results in a non-blocking invocation. -r -b What’s the difference between a blocking invocation and a non-blocking invocation? It’s analogous to a synchronous versus asynchronous method invocation. When you use a blocking invocation, you get your result ( ). When you use a non-blocking invocation, you get an activation ID returned to you immediately. At some point in the future, you can use this activation ID to look up the result and other information about the activation. Read the if you want more detail. with or without metadata documentation Mitigating Cold-Start Delays I said above, what I did was wrong… There are two problems here. ● First, unless you are writing a dirt-simple action, you’re not going to want to put all the code in one file. ● Second, with the approach we’ve just taken, the action will have a cold-start delay. Notice that the above command to create an action uses Swift . But, of course, Swift is a compiled language, so before your action can be run, the Swift source has to be compiled. Compilation does not occur until the first time your action is invoked; this is called a cold-start and causes a delay in the running of your action. After the initial invocation, the compiled program is cached so subsequent invocations usually do not have a delay. “Usually,” because as your application horizontally scales, there will be occasional cold starts when additional containers are spun up. source If possible, it would be preferable to mitigate cold start delays. In fact, I can avoid both problems by re-organizing the code into a more modular structure and creating the action with a pre-compiled Swift program. First, I’ll re-organize the code. Make sure you are in your directory. Here are the steps: yeast Create a SPM Package. swift package init — type=executable In the directory, add as a subdirectory. Move the files , , and to . Sources YeastModels Item.swift Order.swift Receipt.swift YeastModels mkdir Sources/YeastModelsmv Item.swift Sources/YeastModelsmv Order.swift Sources/YeastModelsmv Receipt.swift Sources/YeastModels Edit in to read as follows: main.swift Sources/yeast import YeastModels public struct Output: Codable {let receipt: String} public struct Input: Codable {let items: [Item]} func main(param: Input, completion: (Output?, Error?) -> Void) -> Void {let receipt = Receipt(with: param.items)completion(Output(receipt: “\(receipt)”), nil)} Update to include a target for YeastModels and add YeastModels as a dependency for the main target. will look as follows: Package.swift Package.swift // swift-tools-version:4.0import PackageDescription let package = Package(name: “yeast”,targets: [.target(name: “yeast”,dependencies: [“YeastModels”]),.target(name: “YeastModels”,dependencies: []),]) Delete ) in the directory. Yeast.swift yeast Mitigating Cross Compilation Issues Now I have to compile it. But there are two small hitches: the OpenWhisk infrastructure needs to inject a small bit of code into the Swift program, and cross-compilation. To be fair, cross-compilation is only an issue if you are not developing on Ubuntu. I typically code on a Mac so cross-compilation is an issue for me and probably for you as well. So, I need to somehow build a Linux binary. The easiest way to do this is to use Docker. Make sure you have the latest version of docker installed and that the daemon is running. Here we go: docker run — rm -it -v “$(pwd):/owexec” ibmfunctions/action-swift-v4.1 bash cp /swift4Action/spm-build/Sources/Action/_Whisk.swift /owexec/Sources/yeast cat swift4Action/epilogue.swift >> owexec/Sources/yeast/main.swift echo ‘_run_main(mainFunction:main)’ >> owexec/Sources/yeast/main.swift echo ‘_ = _whisk_semaphore.wait(timeout: .distantFuture)’ >> owexec/Sources/yeast/main.swift cd owexec/ swift build -c release mv .build/release/yeast .build/release/Action zip yeast.zip .build/release/Action Now, quit the shell (type Control-D) which stops the Docker container. What did I do? The first command starts up a Docker instance and launches the Bash shell. The instance makes use of an image, , that IBM provides. This image contains the Swift compiler and the necessary bits of infrastructure to build the action. Docker’s gives you more details, but the flags make it possible to interact with the container from your host’s shell and they make your action’s directory visible from inside the Docker container. ibmfunctions/action-swift-v4.1 documentation The next four commands inject the infrastructure code into our Swift project. Finally, the last four commands build the project and package it according to how OpenWhisk expects it. In particular, OpenWhisk assumes the Swift executable is named . Action Update the action to use the newly-compiled binary: ibmcloud fn action update yeast yeast.zip — kind swift:4.1 Test it again: ibmcloud fn action invoke -r yeast — param-file params.json You should get the same results as before ( ). I’ll leave it as an exercise to the reader to explore the speed-up of a warm-start versus a cold-start. again, modulo the timestamp Now, of course, you will want to automate the build process. In fact, if you read through the IBM documentation, they provide a shell script to do the compilation for you. However, the script makes a few assumptions about the directory structure for your actions that may not mesh with how you organize your code. More importantly, it does not properly handle SPM file structure; it expects your code to be directly under the directory, whereas SPM wants your code to be in a sub-directory of . modern Sources Sources I recommend building a few by hand, and then writing a script once you get a feel for how you want to set up your environment. On a Roll Now that the basic idea works, the same next steps arise as we had with Part 1. Namely, integrating this into the rest of the infrastructure. IBM Cloud provides an API Gateway along with several other mechanisms for triggering your action. Since I wrote Part 1, server-side (and serverless) Swift continue to advance. For example, the seems to be getting some traction for writing services in Swift. Smoke framework In the meantime, I will continue to put off actually running my bakery in favor of exploring new technology. Perhaps my bakery needs a ? Swift blockchain