Search code examples
erlangbeagleboneblackbeagleboardslack

How can this Erlang sys.config be decomposed?


Given an Erlang sys.config that looks like this:

[
    {mousetrap, [
        {slack_user, "mousetrap"},
        {slack_channel, "#mousetrap"},
        {slack_token, "<slack token here>"},
        {pins_export_file, "/sys/class/gpio/export"},
        {pins_root_directory, "/sys/class/gpio/gpio"},
        {pins, [
                {gpio0, 30, "1 (over workshop door)"},
                {gpio0, 31, "2 (by basement freezer)"},
                {gpio1, 16, "3 (in the kitchen pantry)"},
                {gpio0, 5, "4 (Not yet wired)"}
        ]},
        {quiet_minutes, 5},
        {pin_check_interval_seconds, 1}
    ]}
].

I'd like to break it out so that the settings for each component are separated within the config file. For example, the pin_library for the BeagleBone really has no reason to include knowledge of the overall mousetrap app, so it makes sense to break it out. Likewise for the notification_library that sends messages to Slack:

[
    {mousetrap, [
        {pins, [
                {gpio0, 30, "1 (over workshop door)"},
                {gpio0, 31, "2 (by basement freezer)"},
                {gpio1, 16, "3 (in the kitchen pantry)"},
                {gpio0, 5, "4 (Not yet wired)"}
        ]},
        {quiet_minutes, 5}
    ]},
    {pin_server, [
        {pins_export_file, "/sys/class/gpio/export"},
        {pins_root_directory, "/sys/class/gpio/gpio"},
        {pin_check_interval_seconds, 1}
    ]},
    {notification_library, [
        {slack_user, "mousetrap"},
        {slack_channel, "#mousetrap"},
        {slack_token, "<slack token here>"}
    ]}
].

However, this doesn't work because pin_server and notification_library are not apps, which is what this construct requires within sys.config. What would be a proper Erlang way to break out these settings into suitable categories, so that pin_library.erl could change from using:

{ok, PinsRootDirectory} = application:get_env(mousetrap, pins_root_directory),

to using

{ok, PinsRootDirectory} = application:get_env(pin_server, pins_root_directory),

EDIT:

Just to clarify why I think this is important. The components pin_server and notification_library attempt to comply with SRP. But when pin_server invokes application:get_env(mousetrap, pins_root_directory), it breaks the wall of SRP because it creates a dependency on a component where it should not have a dependency. That is, on the mousetrap app. Now it cannot be reused in other apps without a code change. Same goes for the notification_library. The pin_server is potentially applicable to any application that wants to interrogate the BBB's pins. The notification_library is useful in any app that wants to send a Slack notification. Neither should reference the mousetrap app, because they shouldn't have any 'knowledge' of that app.

EDIT:

Based on @michael's guidance, I started by breaking out slack notification to a separate OTP Library at https://github.com/DonBranson/slack.


Solution

  • Since Erlang does not in any way control the access between applications and environment within a node (any application can access any other applications environment), I'm not sure what you really gain by this subtle change of use... and in truth, if it is logical that pin_server is clearly and definitely a part of the mousetrap application, I think you should just not try to do this; work with the system as it is supposed to be used instead.

    Having said this though, because there is no access control as such, you CAN set environment for applications that don't exist like this:

    application:set_env(nonexisting, foo, bar).
    application:set_env(anotherphantom, bat, baz).
    

    But if you run up a node with non existing applications in the sys.config, the configuration for those applications will not be loaded.

    A hack you could do

    You COULD have your application start read your environment and set up the environment of the not-really-existing pin_server application, and unset environment from the mousetrap application, before starting the top level supervisor. This would achieve what you want, but...

    I wouldn't recommend this though even though it is possible, as there's obviously some danger the release manager wouldn't spot a conflict if an application of the name you purloined was later introduced.

    Better alternatives (applications and included applications)

    An alternative is to make pin_server an application.

    If the only barrier to this is that the proposed pin_server application would not really make any sense at all except in the context of your mousetrap application, then you could make it an 'included application'. Included applications are applications that have their own app file, and are really just like a normal (primary) application, but they are started within the supervision tree of your primary application (see Included Applications) and would immediately allow you the change of use you want, IF you don't mind creating the extra application.

    Library applications

    Again it's an application, and I don't think this is probably appropriate in your case, but, there is also such a thing as a 'library application', which is an application without servers (you don't need to implement the application callback module, it just becomes a logical grouping for your modules, API and environment). If you create an app file without a mod entry, it becomes a library application. The reason I don't think it's probably appropriate is that pin_server sounds like a server, and thus should be supervised, but this would also permit you the change of use you want.