-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
2720 lines (2542 loc) · 103 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="title" content="Maestro" />
<meta
name="description"
content="Maestro is a framework for providing rapid iteration for serverless orchestration"
/>
<meta name="type" content="website" />
<meta name="url" content="https://maestro-framework.github.io/" />
<meta
name="image"
content="/assets/images/logos/maestro/full-on-light.png"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="author" content="Torrel Moseley, Zac Klammer, John Isom" />
<title>Maestro</title>
<link rel="stylesheet" href="assets/css/fonts.css" />
<link rel="stylesheet" href="assets/css/main.css" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="assets/images/logos/maestro/favicon.png"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/dark.min.css"
charset="utf-8"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="assets/scripts/application.js"></script>
<script src="assets/scripts/slideshow.js"></script>
</head>
<body>
<div class="logo-links">
<img
src="assets/images/menu-on-light.png"
alt="Maestro logo"
id="menu-logo"
/>
<a href="https://github.com/maestro-framework/maestro" target="_blank">
<img
src="assets/images/icons/github_black.png"
alt="GitHub logo"
id="github-logo"
/>
</a>
</div>
<nav id="site-navigation">
<ul>
<li>
<a href="#home" id="home-link">HOME</a>
</li>
<li>
<a href="#case-study" id="case-study-link">CASE STUDY</a>
<nav id="case-study-mobile">
<ul></ul>
</nav>
</li>
<li>
<a href="#our-team" id="our-team-link">OUR TEAM</a>
</li>
</ul>
</nav>
<header id="home">
<figure>
<img
src="assets/images/logos/maestro/with-subtitle-on-light.png"
alt="Maestro logo"
/>
</figure>
</header>
<main>
<!-- for linkedin featured section -->
<img
style="display: none;"
src="assets/images/logos/maestro/mark-on-light.png"
/>
<!-- normal content now resumes -->
<section id="case-study">
<h1>Case Study</h1>
<nav>
<ul></ul>
</nav>
<h2 id="introduction">1. Introduction</h2>
<h3>1.1 What is Maestro?</h3>
<p>
The rise of serverless architectures and FaaS offerings such as AWS
Lambda has revolutionized how companies are developing modern apps.
The need for an orchestration layer over these architectures has
brought about services such as AWS Step Functions. However, deploying
apps that use Step Functions can be tedious and error-prone. Maestro
prioritizes speed and developer productivity by automating this
process so that the developer’s focus stays on developing their
application’s business logic.
</p>
<p>
Maestro is an open-source, easy-to-use framework for deploying
serverless workflows using Node.js® and AWS Step Functions. Using
Maestro aids development not only in the initial phase of a project
but throughout the ongoing maintenance as well.
</p>
<h2 id="serverless">2. Serverless</h2>
<p>
The popularity of developing applications with a serverless approach
is relatively new. The term “serverless“ seems to imply that servers
are not involved in an application’s architecture, but that’s not
accurate. The reason for the term “serverless“ is not because there’s
an absence of servers, but because the control of the servers has been
abstracted away and made the responsibility of the cloud provider. So
from the perspective of the developer, there’s less need to worry
about maintaining and scaling servers, effectively making their
architecture serverless.
</p>
<p>
Besides being a bit of a misnomer, the term “serverless“ is also a bit
overloaded. It encompasses a few different ideas. One of these is what
is known as Function as a Service (FaaS), or serverless functions.
</p>
<h3>2.1 FaaS</h3>
<p>
Serverless functions are meant to be small pieces of application code
that are provisioned in response to some event. Once the event fires,
a container is spun up in which the function will execute. After
execution, the container will remain idle for a short amount of time
in case there are further invocations of the function. If not, the
container is torn down. This facilitates scaling up and down according
to demand and only paying for the resources that are used. FaaS is so
prevalent in serverless architectures that it is often equated with
the term “serverless“ itself.
</p>
<figure>
<img
src="assets/images/serverless-poll.svg"
alt="58% of developers in one poll respond that serverless to them is FaaS"
title="Serverless Poll"
/>
<figcaption>
A recent poll asking developers how they view the term “serverless“
[<a href="#reference_1" class="reference">1</a>]
</figcaption>
</figure>
<h3>2.2 AWS Lambda</h3>
<p>
We’ll be limiting our discussion to a specific cloud provider, Amazon
Web Services (AWS), as it is by far the most popular provider at this
time.
</p>
<p>
Amazon’s FaaS offering is known as AWS Lambda. Lambdas are deployed on
containers that support runtimes in seven languages [<a
href="#reference_2"
class="reference"
>2</a
>]. Lambdas can be invoked by many AWS services including other
Lambdas. They have a limited execution of 15 minutes, and any state
associated with the execution is then lost.
</p>
<figure>
<img
src="assets/images/logos/aws/lambda.png"
alt="AWS Lambda Logo"
title="Lambda"
/>
<figcaption>AWS Lambda</figcaption>
</figure>
<h3>2.3 Serverless Patterns</h3>
<p>
As serverless development is still relatively new, best practices have
yet to be firmly established. However, there are helpful patterns that
have emerged as developers continue to work with serverless.
</p>
<p>
For example, when a Lambda is initially invoked, its container will
have to be spun up, usually taking a few hundred milliseconds. This is
often referred to as a “cold start”. To address this issue, there is a
pattern called the ‘Function Warmer’, which keeps the Lambda’s
container available for further invocations. Patterns for working with
serverless can be grouped into five categories [<a
href="#reference_3"
class="reference"
>3</a
>].
</p>
<ul id="serverless-patterns">
<li>Availability</li>
<li>Event Management</li>
<li>Communication</li>
<li>Authorization</li>
<li class="highlight">Orchestration</li>
</ul>
<p>
We’ll be focusing our attention on this last group of patterns:
Orchestration.
</p>
<h2 id="orchestration">3. Orchestration</h2>
<p>
Serverless orchestration is the act of managing the workflow between
multiple serverless resources. To visualize what this might look like,
let’s consider a hypothetical situation. Let’s say that we’ve been
assigned a project to create an internal application for our company.
The app will allow an employee to send a request to their manager,
asking that a certain resource be provisioned. If the manager
approves, the resource is provisioned and the employee is granted
access. Otherwise, the employee is notified that their request has
been denied.
</p>
<figure>
<img
src="assets/images/workflow/overview.png"
alt="Initial components in workflow derived from task"
title="Thin workflow"
class="example-workflow"
/>
<figcaption>
Example of a serverless workflow with multiple Lambdas
</figcaption>
</figure>
<p>
Let’s say that the workflow can be implemented by representing each
component as a Lambda. The work of each Lambda must then be
orchestrated to ensure that the workflow executes in a meaningful way.
How will we make sure this workflow proceeds as desired?
</p>
<p>
Let’s assume the ‘Manager’ Lambda will receive an input that contains
information about the resource being requested. What happens if that
input is somehow malformed? An error could occur leading to the
‘Manager’ failing. Some form of error handling could prevent this, but
should the ‘Manager’ validate its own input?
</p>
<figure>
<img
src="assets/images/workflow/validation.png"
alt="Invalid input"
class="example-workflow"
/>
<figcaption>
We need to consider how to handle an invalid input
</figcaption>
</figure>
<h3>3.1 Orchestration Patterns</h3>
<p>
We could introduce a component that has the single responsibility of
validating input. This would provide a clear separation of concerns
and would prevent the ‘Manager’ from failing. A common orchestration
pattern that could be helpful here is known as the ‘Proxy’ [<a
href="#reference_3"
class="reference"
>3</a
>]. The ‘Proxy’ is a Lambda that sits between two other Lambdas and
invokes the second one when appropriate. In this case, we could insert
a ‘Proxy’ Lambda called ‘Validator’ to help handle invalid inputs.
</p>
<figure>
<img
src="assets/images/workflow/validator.png"
alt="Inserting a proxy lambda in front of the Manager component"
title="Proxy Lambda"
class="example-workflow"
/>
<figcaption>
Using the ‘Proxy’ pattern for validating input
</figcaption>
</figure>
<p>
Next, we need to determine how to properly branch based on the
‘Manager’ Lambda’s output.
</p>
<figure>
<img
src="assets/images/workflow/branching-logic.png"
alt="Branching step after the Manager makes a decision"
title="Branching"
class="example-workflow"
/>
<figcaption>How will we implement branching logic?</figcaption>
</figure>
<p>
There’s another pattern we could use called the ‘Router’, which simply
distributes the execution based on payload [<a
href="#reference_3"
class="reference"
>3</a
>]. Since the ‘Manager’ Lambda can invoke other Lambdas, we can
implement the ‘Router’ pattern by placing the branching logic there.
Because we’ve now written orchestration logic alongside business
logic, we’ll affectionately refer to the ‘Manager’ as the ‘Fat
Manager’.
</p>
<figure>
<img
src="assets/images/workflow/fat-manager.png"
alt="Workflow with the Fat Manager Lambda"
title="Fat Manager"
class="example-workflow"
/>
<figcaption>
The ‘Manager’ Lambda now contains orchestration logic
</figcaption>
</figure>
<p>
Now that we’ve taken care of branching, an attempt to provision the
desired resource can be made. However, the provisioning of a resource
may be risky. What if the attempt fails for some reason?
</p>
<figure>
<img
src="assets/images/workflow/provisioning-fail.png"
alt="Failure depicted on the Provisioner component"
title="Failed Provisioner"
class="example-workflow"
/>
<figcaption>The provisioning step fails…</figcaption>
</figure>
<p>
We’d like to prevent the employee from having to repeat their request
such that the workflow has to start over. To address this, we could
once again apply the ‘Router’ pattern. This time, we’ll add logic to
the ‘Manager’ so that it monitors the output of the ‘Provisioner’. If
the ‘Manager’ detects that the provisioning has failed, we can have it
invoke the ‘Provisioner’ again. But how many retries do we want to
allow? How quickly do we want to retry? This is further orchestration
logic that we’ll have to write into the ‘Manager’, making it even
fatter.
</p>
<figure>
<img
src="assets/images/workflow/fatter-manager.png"
alt="Addition of retry to the Fat Manager Lambda"
title="Fat Manager with Retry"
class="example-workflow"
/>
<figcaption>
The ‘Manager’ Lambda with even more orchestration responsibility
</figcaption>
</figure>
<p>
Do any limitations exist with this implementation? The probability of
the Lambda surpassing its execution limit is now increased if the
‘Manager’ monitors the ‘Provisioner’ and there are multiple retries.
Delayed provisioning may cause the ‘Manager’ to terminate before
determining if a retry was necessary. Further, with the orchestration
logic of the ‘Manager’ written alongside the business logic, the
component is overloaded resulting in poor separation of concerns.
</p>
<figure>
<img
src="assets/images/workflow/everything.png"
alt="Overloaded Workflow"
class="example-workflow"
/>
<figcaption>Workflow overloaded with orchestration logic</figcaption>
</figure>
<p>
The consequences of not separating concerns would be felt every time
it’s necessary to iterate on the workflow. Business logic as well as
orchestration logic may need to be evaluated and adapted for the
application to grow.
</p>
<p>
Perhaps refactoring is in order. Maybe it would be wise to extract the
orchestration logic to a separate component other than the ‘Manager’.
If that intuition seems natural, can more be done?
</p>
<p>
Why not extract out an entire orchestration layer? The result would be
a thin workflow of strictly business logic resembling the original
workflow. The orchestration logic would be entirely on its own.
</p>
<figure>
<img
src="assets/images/workflow/orchestration-layer.png"
alt="Diagram of only the orchestration layer in a workflow"
title="Orchestration Layer"
class="example-workflow"
/>
<figcaption>
An orchestration layer abstracted from the workflow
</figcaption>
</figure>
<p>
By implementing the application in this way, we’re able to orchestrate
error handling, retry, and branching all in one place. When the
business logic needs to be modified, the orchestration logic can
likely stay the same. This can be quite valuable for a new application
or one that simply hasn’t reached a stable state.
</p>
<p>
So how would we go about extracting an orchestration layer? Creating
and maintaining custom logic would require considerable overhead.
Let’s look at another pattern called the ‘State Machine’ [<a
href="#reference_3"
class="reference"
>3</a
>] to see whether it might be suitable for our needs.
</p>
<h3>3.2 The State Machine Pattern</h3>
<blockquote cite="https://www.serverlesschats.com/16/">
<p>
"State machines are really just a mathematical way of modeling an
application.…[With state machines,] you can describe your
application as a mixture of your
<span class="highlight">inputs</span>, the
<span class="highlight">states</span> that your application can be
in, and the <span class="highlight">transitions</span> between those
states."
</p>
<footer>
Rowan Udell,
<cite
>Serverless Chats Podcast – Episode #16 [<a
href="#reference_4"
class="reference"
>4</a
>]</cite
>
</footer>
</blockquote>
<p>
It’s important to note that a state machine is not a physical device
but an abstract concept. A state machine is all about inputs, outputs,
transitions, and state, making it an ideal candidate for an
orchestration layer. But how would we go about implementing a state
machine? It just so happens that AWS offers a “State Machine as a
Service” called AWS Step Functions.
</p>
<h2 id="aws-step-functions">4. AWS Step Functions</h2>
<blockquote
cite="https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html"
>
<p>
“AWS Step Functions is a web service that enables you to coordinate
the components of distributed applications and microservices using
visual workflows. You build applications from individual components
that each perform a discrete function, or task, allowing you to
scale and
<span class="highlight">change applications quickly</span>.”
</p>
<footer>
AWS Documentation,
<cite
>What is AWS Step Functions? [<a
href="#reference_5"
class="reference"
>5</a
>]</cite
>
</footer>
</blockquote>
<figure>
<img
src="assets/images/logos/aws/step-functions.png"
alt="AWS Step Functions Logo"
title="Step Functions Logo"
/>
<figcaption>AWS Step Functions</figcaption>
</figure>
<p>
Step Functions are designed to contain an application’s orchestration
logic. This helps us to maintain the separation of concerns that we
mentioned earlier. To create a state machine with Step Functions,
we’ll need to use the JSON-based language called Amazon States
Language (ASL).
</p>
<h3>4.1 Amazon States Language</h3>
<p>
Let’s first look at a “Hello World” state machine definition to
familiarize ourselves with ASL.
</p>
<figure>
<div class="flex">
<pre>
<code class="js dark">
{
"StartAt": "Hello World",
"States": {
"Hello": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:hello",
"Next": "World"
},
"World": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:world",
"End": true
}
}
}
</code>
</pre>
<img
class="maestro-border"
src="assets/images/step-functions-console/hello-world.png"
alt="Example of the ASL of a workflow for a Hello World Application"
title="Hello World ASL "
/>
</div>
<figcaption>
A state machine definition with a visual that is displayed in the
AWS Management Console
</figcaption>
</figure>
<p>
There are two top-level fields required for all state machines:
<code>StartAt</code> and <code>States</code>. The
<code>StartAt</code> field simply designates which state will begin
execution, whereas the <code>States</code> field is a list of all
possible states. Once a valid definition is entered in the AWS
Management Console, a visual that represents the state machine will be
displayed.
</p>
<p>
A state‘s type dictates the additional attributes that the state can
have. There are eight different state types given to us by ASL.
</p>
<ul>
<li><code>Task</code></li>
<li><code>Parallel</code></li>
<li><code>Map</code></li>
<li><code>Pass</code></li>
<li><code>Wait</code></li>
<li><code>Choice</code></li>
<li><code>Succeed</code></li>
<li><code>Fail</code></li>
</ul>
<p>
The <code>Task</code> type allows a state to reference a Lambda
function. This is done by setting the state‘s
<code>Resource</code> field to the Lambda‘s Amazon Resource Name
(ARN). We see that the “Hello World” example has two
<code>Task</code> states: <code>Hello</code> and <code>World</code>,
each of which points to a different Lambda. When
<code>Hello</code> returns, its output is passed as input to the next
(and last) step in the execution, <code>World</code>.
</p>
<p>
The other state types do not map to Lambda functions but help with
workflow orchestration. For example, the <code>Choice</code> state
enables branching logic and the <code>Wait</code> state will pause
execution for some specified amount of time.
</p>
<p>
Let’s return to our earlier example of an employee requesting approval
for a resource to be provisioned. We’ll now implement this workflow
with Step Functions.
</p>
<figure>
<div class="flex">
<pre>
<code class="js dark">
{
"StartAt": "Requester",
"States": {
"Requester": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:WORKFLOW_NAME_requester",
"Next": "Manager"
},
"Manager": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:WORKFLOW_NAME_manager",
"Next": "ManagerChoice"
},
"ManagerChoice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.decision",
"StringEquals": "accept",
"Next": "Provisioner"
},
{
"Variable": "$.decision",
"StringEquals": "deny",
"Next": "Notifier"
}
]
},
"Provisioner": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:WORKFLOW_NAME_provisioner",
"Next": "AccessGranter",
"Retry": [
{
"ErrorEquals": ["States.ALL"],
"IntervalSeconds": 2,
"BackoffRate": 1.5,
"MaxAttempts": 5
}
],
"Catch": [
{
"ErrorEquals": ["States.ALL"],
"ResultPath": "$.errorInfo",
"Next": "Notifier"
}
]
},
"AccessGranter": {
"Type": "Pass",
"Next": "Notifier"
},
"Notifier": {
"Type": "Pass",
"End": true
}
}
}
</code>
</pre>
<img
src="assets/images/step-functions-console/example-workflow.png"
alt="Visualization of example workflow in AWS Management Console"
/>
</div>
<figcaption>
Earlier example now implemented with AWS Step Functions
</figcaption>
</figure>
<p>
One of the states, <code>Provisioner</code>, is mapped to the Lambda
that handles resource provisioning. Looking more closely at this
state, we see how one might implement retry logic.
<code>Task</code> states have an optional field called
<code>Retry</code> that will specify which types of errors to retry,
how many seconds to wait in between, as well as a backoff rate and a
maximum number of attempts. It’s useful to be able to put this
orchestration concern here, instead of writing some custom retry logic
alongside business logic.
</p>
<figure>
<div class="flex">
<pre>
<code class="js dark">
//…
"Provisioner": {
"Type": "Task",
"Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:provisioner",
"Next": "AccessGranter",
"Retry": [
{
"ErrorEquals": ["States.ALL"],
"IntervalSeconds": 2,
"BackoffRate": 1.5,
"MaxAttempts": 5
}
],
//…
},
</code>
</pre>
</div>
<figcaption>Provisioner state showing retry logic in ASL</figcaption>
</figure>
<h3>4.2 Execution Event History</h3>
<p>
Once a state machine has executed, using the AWS Management Console,
we’re able to access an interactive execution event history. This
allows us to audit the transitions between states in one, unified
place.
</p>
<figure>
<img
class="maestro-border"
src="assets/images/step-functions-console/logs.gif"
alt="Execution event history"
/>
<figcaption>
Execution Event History in the AWS Management Console
</figcaption>
</figure>
<p>
Additionally, the visual workflow will be color-coded to help us
quickly remember the outcome of each state for that execution. This
interface is updated in real-time, which can be helpful when viewing
the status of longer-running state machines.
</p>
<figure>
<img
class="maestro-border"
src="assets/images/step-functions-console/execution.gif"
alt="Visualization of an executing state machine in the AWS Management Console"
/>
<figcaption>
An executing state machine in the AWS Management Console
</figcaption>
</figure>
<p>
The core purpose of AWS Step Functions is to provide an orchestration
layer, preventing us from having to somehow choreograph our
application’s resources. Step Functions also provides many built-in
features, such as eight state types, unified logs, and visual
workflows that can help when needing to audit executions.
</p>
<p>
Despite these benefits, some challenges arise when choosing to develop
with Step Functions. Let’s examine some of these problems.
</p>
<h2 id="problems">5. Problems</h2>
<h3>5.1 Programming Model</h3>
<p>
One of the common complaints with Step Functions is that ASL is not
very intuitive.
</p>
<blockquote
cite="https://www.serverless.com/aws-step-functions#drawbacks"
>
<p>
Amazon States Language is quite complex; its syntax is based on JSON
and therefore
<span class="highlight">optimized for machine readability</span>
instead of readability by humans.
</p>
<footer>
Article from
<cite
>Serverless Framework [<a href="#reference_6" class="reference"
>6</a
>]
</cite>
</footer>
</blockquote>
<p>
Imagine creating a state machine with dozens or even hundreds of
states.
</p>
<blockquote
cite="https://research.chalmers.se/publication/508147/file/508147_Fulltext.pdf"
>
<p>
“ ‘…
<span class="highlight"
>Nobody wants to write JSON format for a state machine that has
hundreds of states</span
>.’ -Interviewee 1
</p>
<p>
There is an expectation that, in the future, higher-level
abstractions will be developed and current technologies, such as
Lambda and Step Functions, will become mere deployment platforms,
which are
<span class="highlight">not intended to be programmed directly</span
>.”
</p>
<footer>
Philipp Leitner et al. –
<cite
>Paper from Chalmers University of Technology [<a
href="#reference_1"
class="reference"
>1</a
>]</cite
>
</footer>
</blockquote>
<p>
Several tools attempt to provide higher-level abstractions that
compile down to ASL. However, none has been widely adopted and there
is still unsettled debate regarding the characteristics of these tools
[<a href="reference_7" class="reference">7</a>].
</p>
<p>
It seems that, for now, the most effective way to alleviate the pain
of working with ASL is to use templates. This at least provides a
useful starting point for developers new to Step Functions and is a
nice convenience for those who are more familiar with ASL.
</p>
<h3>5.2 Deployment</h3>
<p>
The AWS Management Console’s tagline is “Everything you need to access
and manage the AWS cloud — in one web interface.” [<a
href="reference_8"
class="reference"
>8</a
>]
</p>
<figure>
<img
src="assets/images/logos/aws/management-console.png"
alt="AWS Management Console"
/>
<figcaption>AWS Management Console</figcaption>
</figure>
<p>
Let’s look at the steps involved to deploy our example workflow using
only the AWS Management Console.
</p>
<figure>
<img
src="assets/images/hazy-diagrams/full.png"
alt="Resources to be deployed manually"
/>
<figcaption>
Deploying our example workflow with the AWS Management Console
</figcaption>
</figure>
<p>
The Management Console indeed provides everything needed for managing
the AWS cloud. But it also requires extensive configuration and
navigation – pointing, clicking, reloading of the screen, often in a
rigid order. The three Lambdas in our workflow all have to go through
the same tedious process. Imagine if our example had included ten or
more Lambdas.
</p>
<p>
Although the Management Console touts itself as “one web interface”,
each of the AWS services has a different flavor of that interface with
distinct concepts and interactions. Each service must be configured
with the correct region and account number and must be assigned
security roles with the proper execution permissions. Also, an account
that contains multiple applications will list all services of the same
kind alongside one another, mingling the resources of separate
applications together.
</p>
<figure>
<img
src="assets/images/aws-services-diagram.png"
alt="The AWS Management Console has many interfaces"
/>
<figcaption>
The AWS Management Console encapsulates several different services
</figcaption>
</figure>
<p>
What would it look like if we decided to tear down our state machine
and its associated Lambdas? It would be a similar, error-prone process
but in reverse order. This is not conducive to quickly developing a
new workflow that requires frequent iteration.
</p>
<p>
The manual tedium of deploying to the AWS cloud using only the
Management Console is a process that should be automated. Fortunately,
several serverless deployment frameworks aim to do just that.
</p>
<h2 id="existing-solutions">6. Existing Solutions</h2>
<p>
A framework for deploying to AWS will typically be built using one of
two AWS tools. Let’s first understand a bit about these tools before
examining any specific frameworks.
</p>
<p>
The first one we’ll look at is called AWS CloudFormation [<a
href="#reference_9"
class="reference"
>9</a
>]. With this approach, a framework can guide us through a process of
creating framework-specific files that are then compiled to a single
CloudFormation config file. All our serverless resources can then be
deployed using this file. Frameworks that aim to be more
general-purpose often take the CloudFormation approach.
</p>
<figure>
<img
src="assets/images/logos/aws/cloud-formation.png"
alt="CloudFormation Logo"
/>
<figcaption>AWS CloudFormation</figcaption>
</figure>
<p>
The other tool is the AWS Software Development Kit (SDK) [<a
href="#reference_10"
class="reference"
>10</a
>]. This is a collection of APIs that allow us to work directly with
individual resources from our language of choice. Frameworks geared
toward a specific use case or a particular language are usually built
with the SDK.
</p>
<figure>
<img src="assets/images/logos/aws/sdks.png" alt="AWS SDK Logo" />
<figcaption>AWS SDK</figcaption>
</figure>
<p>
Let’s now examine some specific frameworks, keeping in mind that we’ll
only be considering those that support Step Functions.
</p>
<h3>6.1 Serverless Framework</h3>
<p>
The aptly named Serverless Framework [<a
href="#reference_11"
class="reference"
>11</a
>] is the dominant player in this space. It supports deployment to
many cloud providers and in several different languages.
</p>
<figure>
<img
src="assets/images/logos/serverless-framework.png"
alt="Serverless Framework logo"
/>
<figcaption>Serverless Framework</figcaption>
</figure>
<p>
Serverless Framework has an active community that has created over two
thousand open-source plugins [<a
href="#reference_12"