New dependency injection haxelib "dodrugs"

116 views
Skip to first unread message

Jason O'Neil

unread,
Apr 25, 2017, 8:38:44 PM4/25/17
to haxe...@googlegroups.com
Hey everyone

Been a long time since I posted, but I've got a cool little side project to share: "dodrugs" a dependency injection library that uses macros to stay light-weight and safe.

Excuse the name.  I wanted to call it "macro inject" or "minject" for short, but that was already taken.  Then I searched for a synonym for "inject" and settled on "do drugs", because both DI and Macros are mind altering experiences when you first learn them.

Motivation

I've used the excellent minject library for years, and it is amazing, powerful and very stable, but I often forgot to inject the things you need, and you don't find out until you hit a runtime error, "Failed to Instantiate SignupController: dependency MailApi not found" or something.

So I wanted to have a go at building a DI library that gives me a nice error at compile time:
test/Example.hx:30: lines 30-35 : Warning : Mapping "Array<Int>" is required here
test/Example.hx:11: lines 11-15 : Please make sure you provide a mapping for "Array<Int>" here
How it looks

Step 1: create an injector and map your dependencies

var inj:Injector<"app"> = Injector.create("app", [
    var age:Int = 29,
    var name:String = "Jason",
    var _:Array<Int> = myArray,
    var _:Connection = Mysql.connect(cnxOptions),
    DbApi // map a class as a singleton
    @:toClass Person // map a class as a class
]);

You create an injector with a unique name, like "app".  It will only let you call "create" with that name once.  And all the things you want to go into that director, have to be defined right there when you create it.  You can create a child injector later, but you can't add anything to an existing injector.  This is how we get our type safety.

The mappings look like variable declarations, with:
  • the name of the mapping
    • or an underscore if you don't care about the name
  • the type of the mapping
  • an initial value
  • There's also syntax for quickly mapping singletons, classes, functions etc.  See the README for more info.
Step 2: have classes with all dependencies requested in the constructor

class DbApi {
    public function new(cnx:Connection) {
        // ...
    }
}
class Person {
    var name:String;
    var age:Int;
    var favouriteNumbers:Array<Int>;
    var db:DbApi;

    public function new(name:Stringage:IntanArray:Array<Int>, db:DbApi) {
        this.name = name;
        this.age = age;
        this.favouriteNumbers = anArray;
        this.db = db;
    }
}

Step 3: Ask for what you want

// Ask for a specific value:
var age = inj.get(var age:Int); // 29

// Create a new Person instance using our injector:
var person = inj.get(Person);
person.name// Jason
person.age// 29
person.favouriteNumbers// myArray
person.db// DbApi, built by injector.
person.db.cnx// The Mysql Connection we injected.

Feedback

I've just pushed a beta to haxelib. 

haxelib install dodrugs

I'd love feedback on the general idea, as well as specific feedback people have from trying to use it.

If there's no major feedback on APIs I should change or bugs that need fixing I intend to commit to the API and release a stable release in the next couple of weeks.

Jason

Thanks Kevin and Juraj for your input on the API design.  Sorry it took me 9 months to take it into account and release something :)

Confidant

unread,
Apr 26, 2017, 11:41:27 AM4/26/17
to Haxe
I like the name. :)
Reply all
Reply to author
Forward
0 new messages