Hi there, happy re:Invent wave two. My name is Nicki, I'm a senior
software engineer here at AWS. I work on the Mobile SDK team
and amplify. So all things Mobile
is usually my domain. But I love serverless. And I have been using Lambda
since it was released back in, I think, 2014, 2015, that timeframe. And I also love Swift, which, coincidentally got released
around the same time. So it's really nice to see
my favorite two things come together. So I'm really excited to be
giving this talk to you today, about how to create
serverless applications in Swift or serverless functions. So, you know,
without further ado, let's dive in. Let's get started. All right, so what are we
going to do today? Well, the first thing
we're going to do is we're going to talk
about the new Swift AWS Lambda runtime.
The Lambda runtime is a project that took a combined effort
from the folks at Apple and AWS, and it is awesome. And I'm hopefully
going to show you everything that you need
to know about it today. I'm going to show you
how to use it, and then we're going to do a demo
of how to create a Lambda, and deploy it to AWS.
And then, you know, we're going to talk a little bit
about performance in the end. And that should be it. Hopefully, you guys will get started
with building your own Swift Lambdas after you watch this talk. All right, so what is it? So the Swift Lambda runtime
is a Swift package. And it's basically
built to help you put out custom
runtime Lambdas in Swift. So let's talk about what custom
Lambda runtimes really are. So you can implement AWS Lambda runtime in
any programming language, right. And what is a runtime?
A runtime is essentially a program that runs a Lambda functions handler
when the function is invoked. And the way that you
distribute a runtime, or the way that you give Lambda
a runtime is through an executable,
called bootstrap. So if you look
at this picture here, I, you know, dissected a Lambda
that I zipped up that I deployed. And you can see that there is
an executable here named bootstrap. The runtime is
essentially responsible for setting up
the function setup code, and communicating
back and forth between the Lambda platform
and your functions handlers. So delivering any events
to the handler and then receiving
those responses and delivering them back
to the Lambda platform. And so that's basically
what it does. So the Swift Lambda runtime
is essentially a library that implements
the Lambda runtime API, and essentially does the work for you
of creating a custom Lambda runtime in obviously the language
of our choice, Swift. So it makes it super easy
to create Swift Lambdas. And we are going to see
exactly how that works. So they wrote several different
APIs for you to write a Lambda. So you can see here,
if you look at the code, we're actually importing
the AWS Lambda runtime, and the Lambda runtime
is easily incorporated into your application
via a Swift package. So you're going to create
a Swift package, right, and then you're going to,
in your package manifest, use the Swift Lambda runtime
as a dependency. And so in my Swift code,
I can then import this module. And then, you know, you see here,
I'm just creating an input. And I'm conforming it
to codable as well, for easy serialization
and deserialization from and to JSON.
same thing with the output. And then all I do
is called Lambda.run. And I essentially
in that closure, I provide my user defined function
or my code, essentially, and then I give the output
that I told Lambda that I was going to give,
you know, in the form of result, so .success, right to the callback. And that's essentially it.
That's the whole thing. It's really, really simple and really
easy to write Lambda functions. So what about events. So, you know, you might
write a Lambda function that receives an event
from somewhere else inside AWS. Maybe you write a Lambda function
that receives an event from an SQSQ, maybe you write a Lambda function that receives an event
from API gateway, to make your, you know, endpoint
available to the outside world. And so who wants
to write all of those inputs as conforming decodable
and exactly what they're? You don't have to. They wrote another module
for you called AWS Lambda events, where you can
just directly use it and you get all of those
essentially struct that already conformed to codable
for you right there, and you just use them
just like any other object. So, you know, in this case,
in this example, I am receiving an SQS message. And so all I had to do
was essentially import it, and then write the type and
that's it, then I can just use it. And so, you know, I did mention
that even API gateway events and, you know,
this is super handy now because you can write
your own back end, especially if you're
an iOS developer, in the language
that you know and love. Just by putting Lambda
and API gateway together, you can easily create
a REST endpoint. And, you know, I have examples
for how to do that. And I will show you in my demo,
exactly how I used Lambda and API gateway in a real life
use case in an iOS application. All right, so what about performance?
It's a good question. So, you know, in this first API
that I showed you, you know, where you call Lambda.run
and you just provide the closure that is going to be
the higher level abstraction API. And what I mean by that is, if you choose to use that API
to write your Lambda code, that Lambda is actually
doing thread switching. And so it's going to run
your user defined code on a separate thread than the one
that's actually interacting with the Lambda platform. So there's a network
processing thread happening, where the runtime engine is
interacting with the Lambda platform, and then your code is running
on a separate thread. Now, because it's running
on a separate thread, there is context switching
between the threads, which means that there is
a bit of a performance hit, not a huge one,
but there is a performance it. So if you have a performance
sensitive function, maybe you're really, really,
you know, you really want your Lambda to be extremely performant
for whatever reason, you might choose to use
the event loop Lambda handler API. And so the way that you would
actually implement this API is, it's a protocol. And you would just
implement it in a struct, and then inside handle,
you would write your code. Now, in this example, in this code
that you're looking at, the code inside handle
is going to run on the same thread that is, again, interacting
with the Lambda platform. So you need to take caution
and care to not write, you know, any blocking code,
essentially, that would block the thread
from communicating with the Lambda platform. So if you're going to use this API,
it's obviously more performant. But you need to be
very sensitive and cautious in the way you approach it, to make sure you're not writing
any blocking code for any reason. And then again, obviously,
all you do is you just pass
your implemented protocol to Lambda.run, and that's it.
And that's how you use this API. So let's talk a little bit
about what's under the hood. So in the higher level,
like abstraction, you are essentially
using the Lambda handler, which implements event
loop Lambda handler for you. And as you can see,
there is an offloadQueue here, because as I mentioned, it's going to run your code
in a separate thread from the thread that's actually communicating
with the Lambda platform. And that's why you see
this offloadQueue here. Now, again, you do take a slight
performance hit when you do that. But if you're just
writing regular Lambdas and you're not really worried about, you know, performance
or cold start necessarily, which in a lot of cases
is not necessarily that important, you can absolutely use this API. And let me just also say,
I have written several Swift Lambdas now and I've used just
the regular run of the mill API, and I haven't really needed
to go beyond that. So I just want to emphasize that, but I did want to include it
in this talk. Because if you did have
something advanced, or extremely performance sensitive, you would definitely want to take
advantage of the other API. Okay, so the other API,
let's take a deeper look at it here. So it is event loop Lambda handler, it conforms to another protocol
called byte buffer Lambda handler. And, you know, nothing
essentially is happening here. There's no offloadQueue,
because again, all the code here is running
on the exact SAMe thread that is communicating
with the Lambda platform. And, you know, it's just doing
encoding and decoding from byte buffers to you,
I mean, for you. And that's essentially that's it,
that's all that this one's doing. And then the byte buffer
Lambda handler, is essentially there to power
those higher level protocols. And, you know, it's interacting
at the byte buffer level. So there's really not
a whole lot of use cases where you might use this one,
but it is available for use if you needed to do
some kind of special serialization. Or you, you know, you had a Lambda
that you needed to interact with the byte buffer level
for whatever reason. So I don't see this one being,
you know, frequently used, but I did put it here, just so that you know about it,
you're aware of it. And you have another tool in
your toolbox in case you needed it. So yeah, essentially, that's the entire
runtime library under the hood. I mean, there's a lot
of other moving parts, but that's the base of it. And so, you know, you've written
your Lambda, and you're like, well, I want to locally test my Lambda
before I deploy it. Good question. How do you do that? It's actually way simpler
than you could think of. They made it super easy for you.
All you have to do is add an environment variable
to your Xcode scheme. And I show you how to do that
in the demo as well. And, you know,
you set the value to true and that's it,
and you can actually just click Play inside Xcode,
and it will run your Lambda locally. I think it defaults to Port 7000, if I'm not missing taken,
on an endpoint. And you can call it locally
and test out your code before you ever deploy it to Lambda. All right, you can also fine tune
with other environment variables. Log level is typically the one
that people want to know about. The logging level is using Swift log.
So if you ever used Swift log, you might be familiar
with the API already. It's going to be set
to info by default. And, you know, there's several
other environment variables that you can use
to fine tune your Lambda function. And you can check those out.
All right. So you're like, I have my Lambda,
have locally tested it, I've fine-tuned it, I chose
the correct, you know, performance API to write my Lambda, how do I actually package
and send it to AWS? Well, there's several ways
that you can do this. The first way is, SAM,
this is my favorite way. This is going to be the best way
and the easiest way to consistently and repeatedly deploy your Lambda
as you're developing it. The second is, you know, you can
manually create a zip and upload it. And then I just want to emphasize
that, you know, all these different ways,
I will provide resources at the end so that you can choose the way
that is works for you. I don't want to tell you how to do
things or what way you should choose, you know,
you find a way that you like. The third way is new, you know, just released last month
at re:Invent. You can essentially give Lambda
a container image directly. And then the last way is not
technically a real thing yet, but I'm hoping by putting in here,
you'll all go out and vote. So there's an RFC
out in the Amplify CLI library. And Amplify CLI,
if you're not familiar, is a really easy way
to create AWS resources without having a lot of AWS
knowledge actually. It's a very, very simple,
easy to use CLI tool. And, you know, you can do something
as simple as Amplify add API, and it essentially will create
a Lambda and API gateway project for you. And wouldn't it be so nice
if you could say Amplify add API, choose, you know, a REST endpoint,
it creates the API gateway Lambda, and then you could choose Swift, and it will deploy the whole thing
for you as simple as amplify push. So that doesn't exist yet. That's hypothetically
what it would look like in my head. I don't know actual implementation.
But ideally, but if you all go vote, hopefully, we can take it up
as something that we actually do. So I'm trying to get
the votes out there, you know, please vote for this RC. There will be a link to it
at the end in my resource page. So hopefully you all vote, and we have a fourth way
to distribute Swift Lambdas, which essentially is going to be
great if you're an iOS developer, because you might already
be using Amplify. Okay, let's see a demo.
Let's get into the demo. Okay, so for this demo,
we're going to do two things today. The first thing I'm going to do is
I'm going to show you how to get up and running and build a Swift Lambda superfast
and deploy it to AWS. And then, for the second thing,
I actually built a full on demonstration of an iOS app
with a Swift Lambda, to show you an actual use case
for writing a Swift Lambda, maybe in your iOS development
if you're an iOS developer. So let's get started.
So the first thing we need to do is we need to create a new
Swift package. So we need to create a new folder. And we're going to go
into our folder. And then let's actually just run
the command to create a new Swift package, fairly simple command, Swift package in it,
and then you give the type, and then we can actually
open this in Xcode. So here we have our Swift package
with our sources. And we're going to actually edit
the package now to take dependency on the Swift
Lambda runtime. So I have this little handy gist
that I wrote, so I can just steal my own code, don't have to write it again,
and replace it here. And then we're going to steal
the products that we want also, in the interest of time, obviously. And then I also want
to run it locally, so I'm going to give it a platform. Okay, so then we click Save, and we should see
our dependency on the left. Check it out, there it is.
It's already been pulled down for us. So we should be able to
just go to main and use it. So we can say import AWS Lambda
runtime, there it is. Okay, so we need to create a struct
as our input for our Lambda and conform it to codable so that it can easily be, you know,
deserialized from JSON. So let's write our struct. And our struct is going to have,
you know, one property in it of type string,
and it's going to be named. This Lambda
is going to be super basic, it's just going to return an output. And this output is going to have
a message of type string. Okay, so now we can use the runtime
to write our actual Lambdas. We're going to call Lambda.run
and we're going to use this guy. So it's going to take a context. And then our input is
going to be of type input. And we have a closure. We had to replace this
because our output is called output. And then we need to name
this parameter. Bingo. So now we can actually just call this
with success, instantiate our output. And, you know, we want our message
to basically be hi to whoever
was given to us for the name. Okay, so now we've written
our cool little simple Lambda, we need to actually test it. So it's actually really easy
to do in Xcode. You can just add an environment
variable called local_lambda, wow if I can type, server_enabled,
and set it to true. And then we should be able
to just click the play button, and it should run it locally. There it is,
running locally on port 7000. And we can basically call /invoke. So we can actually test this locally,
I usually just use Postman. We can literally have the test
already written for us here. We can call /invoke, right,
on port 7000. Name, we can give it a name
for re:Invent 20. And then if we hit it,
we should get our message back of hi re:Invent 2020.
Cool, it works. Okay, so, now you probably want
to deploy your Lambda. So we need a few things,
we're not able to cross compile with Swift package manager yet. Obviously,
there's always a hope that one day, so we need a Docker image
so that we can run, you know, Swift builds for release inside
a Linux Docker image with Swift to actually create the binary
that we need to give to Lambda. I'm going to use SAM.
As I mentioned, you know, before, there are multiple ways
you can deploy to AWS. I created this handy
little resource page for somebody to link to you
at the end, of all the different ways that you could deploy
with instructions for each way, whatever your preference is.
I like to use SAM. My friend, Eric, created this nice
little repo for us with a make file, a Docker file
and a SAM template in it. And so I stole some pieces of this. And so we're actually
going to use SAM to deploy it. So let's create a Docker file. And we need an image. So luckily,
the Swift team over at Apple, they write a bunch of images. So let's just steal this one
for Amazon Linux 2. And let's create our Docker file. Bingo, now we have an image. And then we're going to create
a make file. And this make file
all it's going to do, we're going to steal
this lovely gist that I made. So I actually took the make file
that my friend Eric created, and I edited it slightly
to use static linking. And all that means is I added
these two flags to basically build with static linking,
you know, to improve performance. And then because I statically linked,
I do not need to copy the dependencies,
which is my make file does, but I do need to create
a symbolic link. So I'm doing that for you here. So you can use my make file for
static linking and his for dynamic. Okay, so every time I paste this,
it never works, I always have to end up
opening it in VS code. So let's go ahead
and actually do that. VS code obviously is more handy for,
you know, syntax with the make file
and the Docker file. Xcode doesn't help me out there. So yeah, let's do tab size,
so maybe it'll work. Okay, so now we need
a template.emo file. So let's create one of those. And let's steal his template
and edit it slightly. So he wrote this great
little template, right. It's just a very simple SAM template. And this SAM template
is actually setting up two functions. He has two functions. Here one function
is setting up an API endpoint with a path
for a get method, and the other one is
just creating a function. For the purposes
of this demonstration, we're just going to create
a function, so we don't need this one. And let's see edit
this to be re:Invent Lambda. And then you can see
how we're actually telling SAM that we want the build method
to be make file, which is true. We do not need an output
because we are not spitting out an API endpoint.
And that's basically it. There's our SAM template.
So now what we should be able to do is we should be able
to call SAM build. And, of course, there is a problem. Huh?
Am I in the right directory? Maybe. Okay, different error.
All right. So it looks like there's something
wrong with our make file. So let's just see what happens
when we just run the make file into… oh, here we go. I didn't edit this.
This is my bad. So you need to edit this
with the name of your Lambda, which ours is re:Invent Lambda. Okay, and let's just call it
just to make sure that it works. Okay, I told you that it always
has a formatting problem. Make files very specific
with their tabs, you have to have 4, 1, 2, 3, 4. Okay, let's see.
Bingo, we're in business. Okay, so we actually could have
just called SAM build, we didn't need to
individually build with make. But, you know, we're just testing
and seeing if it works. So once this is done,
we're going to call SAM build, to build our SAM template
and use our make file, essentially. And if you look at our make file, our make file is actually
just calling Docker run on our image. And then it's calling this command
inside the image, Swift build product release
with our static linking flags, and then creating that symbolic link.
And that's all it's doing. There's nothing complicated
about the make file. So, looks like it's almost done
compiling here. And then once we've called SAM build,
we can call SAM deploy. And we can deploy
this thing to Lambda. SAM, in my opinion,
is the easiest way to deploy your custom
runtime Lambda to Lambda. But, you know,
everybody's got a preference. So I did provide multiple ways
to deploy as a Lambda, with instructions,
depending on your preference. I'm definitely a SAM kid. All right. Come on. Come on Swift.
Finish it up. Let's go, 24 out of 24. Any day now.
Real life development for you. Waiting.
I hope it didn't error. I feel like I can hear
my computer like, struggling. Oh, we're getting somewhere. Oh, and there's
apparently more steps. All right, slow.
Normally, if this were live, I would use this opportunity
to interact with you guys. Fortunately,
we are all stuck at home. All right. Let's see. So hopefully this thing
finishes pretty quickly here. And then we can call SAM
build and SAM deploy. Bingo. All right.
Whoops, don't want to type there. That was a mistake.
SAM build. Okay, so SAM build is
essentially using the make file. So it's going to run the same steps
that we just ran. It's pretty pointless to just,
yeah, there we go. It succeeded. So now we can call SAM deploy. And we can actually use
this handy parameter called guided to guide us
to help us to run SAM deploy. Otherwise, you have to pass
the parameters individually. So I love this.
So stack name, re:Invent. What should I call this?
Swift Lambda stack. How about that? And then the region I like
is US-West-2. So this is asking if I want to
confirm changes before deploying. So the next time I deploy,
should it ask me to confirm or not? For the purpose of this demo,
no, doesn't matter. Yes, I do want to allow IAM
role creation. Yes, I want to save my arguments
to a config file. That's a perfectly fine
configuration file name. And the environment could be default. I mean, you might name it prod
or dev or whatever. But this is just a demo, so doesn't
need to be anything beyond that. I'm not going to use this Lambda
anywhere in production. Okay, so now it's basically deploying
the stack to cloud formation. Looks like it's pretty quick,
it's almost done. It's creating the roles necessary,
which is so nice. I love SAM
that does all this for you. And then we should be able to see
our Lambda inside our console, and we can actually
run it there, too. And it should work, hypothetically,
always hypothetically. Cool. It's successfully created.
So let's go to Lambda. And let's search for our Lambda.
There it is. And we can test it using our handy
JSON objects that we know. Get rid of all this. Bam.
And run our test, boom, succeeded. There it is, there's our output.
That's it. That's basically how to quickly create a Swift Lambda
and get it up on AWS. So now I'm going to show you
the actual demo that I built. So I built this app. And all this app is, is it's an app
that loads my in app purchases that I have created
in my App Store Connect account. So I have two consumable things
that somebody can purchase. And so it's just listing them out. So I actually have my phone
here for you, and unlock it. And this is what the app looks like. So it's displaying
both of these products. And this is actually, what it's doing is it's actually calling
a Lambda function to validate the receipt
and displaying a checkmark if there's
a valid receipt for that product, so if that product was purchased.
And if it wasn't purchased, it's just going to display
a Buy button and let you buy it. But I bought both of these products, bought both of these products
before this demo. And so you see how this
actually displaying the checkmark. So the receipt manager is calling a
Swift Lambda to validate the receipt, and it's a Lambda
that I actually deployed. And I'll show you the Lambda
really quickly. But all it is,
is just making the call using URL session to my endpoint. And so if we take a look
at my Swift Lambda, my Swift Lambda is using this awesome package
written by my friend Fabian, it's now been transferred
to another person's repo. But it's still available,
it's open source. And you can you can look for it
App Store receipt validation, I will also provide
a link to it as well. And all it's doing is it's basically
instantiating their client. You need to provide a secret if you have
auto renewable subscription products. And so an easy way to do that
is to just create an environment variable
in your template. So I actually did this
for you in my little templates so that you could come in
and replace it if you have auto
renewable subscriptions that you're going to use. And you can actually pull using
this get environment variable, I wrote this little function
for you as well. So you can literally just come
in here and uncomment this line if you need a secret and pass it in.
But I have a consumable product, so I don't use secrets,
so I just pass nil. And then I'm calling their validate
receipt endpoint with my receipt. So my input is an API gateway event, because I set up, obviously,
API gateway with my template. And then, you know, I extract
my actual input from there, which just has a receipt on it.
I call validate receipt. And then for subscriptions, you know,
if you're validating subscriptions, you want to compare expiration dates.
But for consumable purchase, you know, you just want to make sure
that when the receipt is return, there's something inside in app.
So I can say valid is true, if there's something in there
and spit out my output, encode it and send it back to API gateway,
which sends it back to my app. And then amazingly,
displays that checkmark for you. So that that's basically
it working live. And luck again, for you in action.
So that's pretty awesome. That's the demo I built.
It's going to be up for you and linked for you
in the resource page. And yeah, let's get back
to the slides. All right, so now that you've seen
a demo of how to easily create a Swift Lambda and deploy it to AWS using SAM, and you saw
my cool demonstration of it actually working inside of an iOS app
with a real live use case, let's talk about other performance
tweaks that you could make. So the first one… first of all,
I want to preface this with, I don't want you
to take this to heart and really think heavily about this. Because I know that
if you're an iOS developer, you heavily depend
on the use of foundation. But if you have a performance
sensitive Lambda, something that you could do
is remove all use of foundation. So removing it from your user
defined function, as well as not even using the regular
AWS Lambda runtime module, but using runtime core instead. The regular AWS Lambda runtime module
does make use of foundation for, I believe, encoding and decoding. And if you just use core
and you implement the APIs yourself, you could eliminate
all use of foundation, which would essentially
improve performance. And the second thing you can do,
I actually did in my demo, you can use static linking now
in Swift, 5.3.1. So I showed you, if you remember,
in the make file, there were two flags I added to
actually build with static linking. And it's pretty easy to do. I also will provide a link on the
static linking at the launch of it and a forum post
from the folks at Apple in my resource page at the end.
So you'll have that as well. But that is the image
that you need to take advantage of if you'd like to use static linking, and then you obviously need to use
the flags that I provided, a compile time,
it's fairly easy to do so. Just so you get an understanding
of how big the difference is, when you use static linking, I actually wanted
to put this here for you. So you look at the cold start times,
there's a significant difference between
when you're using static linking and when you're using
dynamic linking. Warm start doesn't affect it
that much, it's pretty much the same, no matter which you choose. But it definitely affects
cold start time. And so if you're sensitive to that, I would definitely compile
with static linking. Dynamic linking, obviously,
you don't really have to do anything. You just, you know, Swift build.
It's fairly easy to do so. But you have to copy
all the dependencies, as I mentioned before,
into your package. So yeah, there's examples
on how to do both. It's up to you, it's your preference, I definitely would prefer
static linking, because I love
that little performance boost. And then you know how to Swift
stack up to all the other
programming languages. If you know if you're
going to take it on, and you've been a serverless
developer for a long time, and you're like,
oh, I'm really interested, I want to get started.
But how does it compare? Well, it falls pretty much
in the middle here. But they've been doing
some serious improvements. So you see here that static linking is significantly higher
than dynamic linking. And it's also now beating Go. So they have made that improvement
since their release in June. So they are consistently
and continuously iterating on the Lambda runtime
and improving its performance. So who knows, maybe next year, I'll
be talking about how it beats Node. By the way, the runtime library
is open source. So if you would like
to contribute to this project, I highly encourage you to do so,
especially if you know anything about how to improve performance
on Lambda, please contribute. Because I would
definitely love to see Swift make it, you know,
to the top of this list, because it's my favorite, obviously. All right, so that's basically it. That's everything you need to know
about Swift Lambdas in a nutshell. That link is, again,
linked to my resource page, which I created for you. It has links to all
the repos of the Lambdas that I've built to date with
the different ways to deploy them. And links to other videos of talks
that I've done before on this topic, as well as all the resources
that I've used and found online
to essentially create Lambdas and deploy them to AWS. That's all I have for you folks. Thank you so much for listening, and I hope you have a great rest
of your re:Invent wave two. And again, my name is Nicki,
and have a great day.