Day 16: Santa CL::AWS (part 2)

… in Part 1 of this winter drama, we left Mrs CL::AWS in a pickle.

The story so far: the elves needed to rebuild their eChristmas website on AWS EC2 – Mrs CL::AWS had quickly whipped up a minimal raku script to use the AWS CLI with a basic procedural coding approach and shell execution of the required commands.

BUT was this code just too procedural? was it too hard to maintain? would the elves be able to pick it up, to grok it and to extend it come next year after their hibernation under the polar ice? Was raku the right choice?

She recalled her mantra “we need a kitchen sink language [groan] where Procedural, CLI and OO and Functional are all first class citizens – why don’t we try raku?”

It was raku to the rescue again. To improve matters, she went back to the keyboard and refactored the version 1 raku procedural CLI code into version 2 raku object oriented code….

Version 1 – raku procedural CLI code:

Authors note: this code really works, just go “apt-get install awscli && aws configure” and run it! Then take a look in your AWS EC2 console. Beware – these examples can quickly ramp your AWS bill!

Version 2 – raku Object Oriented code:

The reindeer pronounced their code reviews:-

Doner said “OMG I just lurv the way that you have taken 41 lines of code and increased it to 152 … that’s a cheeky use of vim folding if ever I saw one … and since we are paid by the line we are quids-in providing we zR

Blitzen said “I was so looking forward to using Inline::Perl5 and calling the PAWS cpan module without a gift wrapper, now you’ve written such a cool added value layer in raku that I can’t resist”

Rudolph said “we should use Python because it has great Functional libraries (ok they’re not actually part of the language) and that way we can structure and compose the methods and abstract away from this low level”

Let’s dig in say the dwarves [huh? ~ed]:

Object Orientation in Raku

I like to think of Raku OO as a gearbox. You can start in 1st gear with “baby raku” as done here. [See the section II in Think Raku for example]. Its quick and easy and echoes other language’s familiar OO tools such as Python and Ruby.

Raku classes are a very simple approach to handle “lumpy” data models. As part of the refactoring, this data model (Instance has Session has KeyPair has name) has emerged from simply arranging things in the most natural place. This avoids the challenges of some other OO languages (C++ in particular) with formal data Structures from the get-go.

Then, as your code evolves, with Raku you can shift gears up through the range to more tightly apply Typing and SOLID principles, polymorphism, roles, multiple inheritance, interfaces and so on.

Mrs CL::AWS is stuck in 1st gear.

class Config {…}

class Config {
    has $.image;
    has $.type;

    method TWEAK {
        my %y := load-yaml:
          '../.racl-config/aws-ec2-launch.yaml'.IO.slurp;
        $!image := %y<instance><image>;
        $!type  := %y<instance><type>;
    }
}

Coding is all about choice. First, choosing which gear. And next, choosing to load Config into a class.

You could choose to load a script-wide config Hash, but here we felt that the class model would serve as definition of what this code expects to find in the YAML file.

In class Config {…}, attributes are declared with has $.image. The ‘$.‘ sigil provides public accessors so that you can get the values like this:

my $c = Config.new;
say $c.image;

Simple, easy to read and familiar.

Could you specify that this attribute is private? – sure, use the $! sigil instead.

Could you check that its a string type? – sure like this … has Str $.image.

Could you specify that it is readonly? – errr, yes.

How about setting a default? yep – has Str $.image = ‘ami-0f540e9f488cfa27d’

…but remember, the sleigh is in 1st gear and the aim is code clarity.

On to method TWEAK {…}, this constructor method is called late in the object creation and gives us the chance to do initialisation of attributes. It runs before the object is finalized with public accessors so we have to access the attributes via their private names $!image.

class Instance {…}

For a while, we agonised about should Instance contain Session or should Session contain Instance. This is precisely the kind of design choice that Raku OO surfaces and, once again, the coder faces a choice.

The decision was finally driven by the calling convention. Ultimately this code could be called on the command line with e.g. raws –ec2 –instance launch. Which returns the ssh connect string to the AWS EC2 instance we just launched.

Already, at the foot, we have Instance.new.connect.say; to do just this.

So, top level, what we want is an Instance and then to call .connect on it.

Therefore, all the other stuff, KeyPair, VPC, Elastic IP and so on should sit within the Instance that has $.s = Session.new; as the place to put it and to marshal it all in the correct sequence.

Which leaves the intent clear to set out and execute the actual awscli call that does the lifting:

my $cmd :=
    "aws ec2 run-instances " ~
    "--image-id {$!c.image} " ~
    "--instance-type {$!c.type} " ~
    "--key-name {$!s.kpn} " ~
    "--security-group-ids {$!s.sg.id}";
    
qqx`$cmd` andthen
    $!id = .&from-json<Instances>[0]<InstanceId>;

Part I of this post explains the Raku goodies at work here.

Authors note: This small code snippet demonstrates whether programming languages should restrict use of white space and tabs.

Stocking Fillers

To wrap up, here are some snippets that Raku helped with on the way and some things that did not make the list:

Using binding := instead of assignment = tells readers that the value is not intended to change.

The <> hash accessors quote key names to cut down on line noise:

$!image := %y<instance><image>;

instead of:

$!image := %y{"instance"}{"image"};

class Config {…} could be a Singleton — but the extra logic is an overhead we do not need as we have control over its use.

We could have chosen to autoload all the YAML fields into the class without regard to the content with access via the FALLBACK method, we could still choose to make the Config class attributes available as a Hash. We could have used the Config module.

method TWEAK {…} could be a submethod instead — but we chose to keep the simplest option to minimize the code wattage since class inheritance is not in play here.

role not class … when changing up to 2nd gear we would probably want to refactor all but the most “noun” like classes into roles … often role can replace class since Raku puns the role anyway. That way lies powerful code composition via mixing in of behaviours.

Functional Future

So, this refactoring from Raku procedural to Raku OO (even in 1st gear) has dramatically improved code clarity and composability.

But as we use the awscli multiple times, would it be nice to be able to have some re-usable and chainable functions like aws().ec2().instance().run() that “know” when they have to generate key-value pairs in the target format like –image-id {$!c.image}, –instance-type {$!c.type} and so on.

In future, maybe it could be as simple as this [since in raku you can drop empty ()s]:

aws.ec2.instance.run($:image-id, $:instance-type);

And so, the Raku Functional features could help us to improve clarity and composability even more!

Authors note: most Functional coding techniques are native to raku and this will may well be a subject for a future post on my blog over at https://p6steve.com

But that’s for another time…

~p6steve

4 thoughts on “Day 16: Santa CL::AWS (part 2)

  1. Great article pair, thanks. But, according to my reading of the docs in “Object orientation,” a class created using ‘has $attr’ is the same as ‘has $!attr’, i.e., private attributes; then the docs get a little muddy to me in the next paragraph, which I think needs a bit of work.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: