Update: With the removal of built-in Ruby support in Alicorn, we've modified this article to focus on the Javascript APIs only

Alicorn V0.7.5 has landed, and with it comes a plethora of minor enhancements, changes and, hopefully, our last significant re-brand for the time being.

Shards

Shards represent the next generation of the Alicorn API, replacing the now deprecated Spheres. Shards provide a whole new set of lifecycle options while still remaining faithful to the simplicity of Spheres.

Why Shards?

Spheres were carried over from when Alicorn was much simpler and electronics-focused, providing a quirky name and a very simple create-update-destroy lifecycle that's more appropriate for something like a closed-loop control system on a robot. However, Spheres had several notable drawbacks:

  • Forcing a piece of code to always run once every millisecond was very immature, as most control systems and services don't actually need to update until some state changes in their consumed data.
  • Spheres had no logical way to group related functionality together and define dependencies on each other; the management system assumed that users were aware of their requirements and would manually check them.
  • Spheres offered no managed way to subscribe to one another without using a shoehorned interface we wrote two years ago.
  • The name "Sphere" made zero sense now that we've moved away from the old Stratos brand.

Types

Unlike Spheres, Shards come in two varieties: class shards and callback shards. This gives Alicorn API consumers the ability to choose the kind of Shard that's most appropriate for their language.

Class Shards

Class shards work just like the old Alicorn Spheres did; simply define a class that extends Shard, implement some methods and you're good to go. Not a whole lot to see here!

Callback Shards

Callback shards are the new hotness for Alicorn V0.7; they enable languages that don't allow for more traditional OOP techniques to define Alicorn shards with minimal hassle. Let's take the following shard that simply prints "Hello!" at a fixed interval:


    public class Hello extends Shard {
        public void update() {
            log("Hello!");
            sleep(1000);
        }
    }

Alicorn will pick up this shard as "Hello" and, when it's created, it'll print out "Hello!" every second. But what if we want to define the same shard in Javascript? There are ways to extend classes, sure, but they're kind of clunky and not intuitive. This is where callback shards come into play. Here's a shard that does the same thing as the one above, but written in Javascript:


    alicorn.shard("Hello")
        .update(function(self) {
            console.log("Hello!");
            self.sleep(1000);
        });

Pretty simple, right? The observant reader will notice one new thing that callback shard have: the self variable. Since a callback shard isn't an actual class, your shards need some other way to store their state and perform API-centric functions (like temporarily sleeping a shard); this is where self comes into play. It provides API users with the full context of the current shard being executed, as well as a suite of helpful functions that can be called.

Lifecycle

Shards bring with them a familiar, but also entirely overhauled, lifecycle. Unlike Spheres, Shards have built-in support for the publication and consumption of Bundles. We'll go ahead and cover each lifecycle item and how it works in detail here.

Create, Update, Destroy and Dependency Management

The create, update and destroy lifecycle phases of a Shard haven't changed a whole lot (besides some internal enhancements). However, there is one cool new thing: dependency management! When using classic Spheres, many of our code examples featured a throws clause at the end of the create phase if a Sphere couldn't be loaded. For example:


    public class ArduinoTest extends Sphere {
        public void create() {
            Sphere arduino = getSphere("Arduino");
            if (arduino == null) throw new ShardException("Couldn't get an Arduino!");
        }
    }

The above code would cause a Sphere to get an instance of the Arduino sphere on startup. If the instance couldn't be obtained, it'd throw an exception. With shards, we've added a much nicer way to do this that also creates a dependency graph, guaranteeing consistent behavior across executions:


    public class ArduinoTest extends Sphere {
        public void create() {
            Sphere arduino = require("Arduino");
        }
    }

The above code will declare a dependency on the "Arduino" shard. If the shard can't be found, the ArduinoTest shard's creation will be automatically cancelled. Additionally, whenever a shard is created as a result of a call to require, it will automatically be destroyed if no other shards need it when the original shard that required it is destroyed. This helps alleviate some memory and CPU bloat for long-running Alicorn instances.

Publish and Consume

With the arrival of shards, we've gotten rid of some old interfaces (like Publisher and Subscriber) and built them directly into shards. Internally, all shards now have a dedicated Bundle backing that enables them to quickly publish data to any shards subscribing to them. Before we added this level of support for messaging, you might have written a simple program that toggles an LED on an Arduino similar to this example:


    public class MyButtonProvider extends Sphere {
        boolean state = false;
        
        public void update() {
            state = // Some state
        }
        
        public boolean getState() {
            return state;
        }
    }
    
    public class ButtonArduino extends Sphere {
        ElectronicSphere arduino;
        MyButtonProvider button;
        
        public void create() {
            arduino = getSphere("Arduino");
            button = getSphere("MyButtonProvider);
        }
        
        public void update() {
            arduino.setDigitalOut(13, button.getState());
        }
    }

The problem with the above code (besides the fact that you need knowledge at compile-time of the MyButtonProvider class) is that a request to change the state of the button on that Arduino is being set hundreds of times per second, even though we know that our user is probably only pressing the button - and thus changing the state of the program - maybe once every few seconds. With the revamped publication support, we can write shards like these (using JS!):


    alicorn.shard("MyButtonProvider")
        .update(function(self) {
            if (someConditionIndicatingNewState) {
                self.set("state", someState);
                self.publish();
            }
        });

    alicorn.shard("ButtonArduino")
        .create(function(self) {
            arduino = self.require("Arduino");

            self.require("MyButtonProvider").consume(function(bundle) {
                arduino.setDigitalOut(13, bundle.get("state"));
            });
        });

So there's a few more curly braces flying around, but we can manage. Right? The above shards will only ever cause the Arduino to get written to when new data is available. This reduces the overall CPU intensity and network I/O of a running Alicorn server and makes the flow of code more predictable.