An IoC Container for Vala

Several days ago, here on Discourse someone asked about an IoC Container for Vala. Reading it, I became curious and started searching about it, finding that there was an apparent IoC Container called libmodulo, but all the references to the code seem to be dead.

Since I’m relatively new to the topic, I started researching control inversion, dependency injection, and how IoC Containers worked in other languages, until I became interested in testing it in Vala as well.

I developed a small Object Factory like the one described by @nielsdg, but more generic, making use of Vala’s reflection abilities, and I improved it over time (several experiments are visible in a group of Russian Telegram about Vala, managed by @gavr) . And now that I think I have something relatively useful (although still very small) I have decided to publish it as a small library.

It is called Vadi, and it only has one class that inherits from GLib.Object, and that implements 4 methods: register_type (), register_factory (), register_instance () and resolve (). It’s kind of inspired of C# Unity and a couple of JS examples of articles on the topic. And the idea of ​​use is as follows:

interface Service : Object {

    public abstract void serve ();
}

class FoodService : Service, Object {

    public void serve () {
        message ("I'm serving food");
    }
}

class Client : Object {

    // You need to declare your dependencies as public construct or
    // construct-only properties
    public Service service { get; construct; }
    
    public void use_service () requires (this.service != null) {
        this.service.serve ();
    }
}

void main () {
    // Then you need to instance and configure the container
    var container = new Vadi.Container ();
    container.register_type (typeof (Service), typeof (FoodService));
    
    var client = (Client) container.resolve (typeof (Client));
    client.use_service (); // Output: "I'm serving food"
}

The clear disadvantages of this is that one cannot define constructors, since the container will ignore them (since it instantiates the objects with GLib.Object.@new ()), but to solve that problem there are the other two ways to register dependencies:

  • With register_factory ()

    container.register_factory (typeof (Service), container => {
        // Here you can define how you want to instantiate the object.
        // You can use the container in order to get dependencies already registered
        return new UsersViewModel (container.resolve (typeof (Database), "123");
    });
    
  • With register_instance ()

    var service = new WebService ("this-is-a-token");
    container.register_instance (typeof (Service), service);
    

I hope someone will find this useful, and if you think this can be improved with new features or ways to make it more friendly with other languages (and not only Vala), feel free to say it here or to open a new issue at GitHub :smiley:

4 Likes

You can override virtual void Object::constructed().

1 Like

Well, I was talking about this

class A : Object {}

class B : Object {

    public A dep { get; construct; }

    public B () {}
    public B.with_dep (A dep) {
        Object (
            dep: dep
        );
    }
}

void main () {
    var container = new Vadi.Container ();
    var b = (B) container.resolve (typeof (B));
    assert_null (b.dep); // This will fail

    // The container will try to intantiate every property marked as construct
    // or construct-only, no matter if it's not registrated.

    // How can I tell to the container to use the first constructor in order to get
    // null on the dep property?

    // Well... with register_factory ();
    container.register_factory (typeof (B), container => new B ());
    b = container.resolve (typeof (B));
    assert_null (b.dep); // Success :D
}