Dependency Injection for Common Lisp
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
example
src
tests
LICENSE.md
README.org
injection-test.asd
injection.asd

README.org

Injection

Dependency Injection for Common Lisp.

Example calls

Set up a config file similar to the one found in examples/config.yml (modeled after the PHP Symfony framework config):

parameters:
  foo: bar
  an_array: [1, 2, 3]
  extra_config_file: "../example/config_only_params.yml"

services:
  file_loader:
    # 'factory:' is a function that will return an instance of the class we require,
    # since Common Lisp doesn't have position based class
    # constructors, a factory function is needed to map them to the class.
    factory: File-Loader-Factory
    arguments: ["%extra_config_file%"]

  service_container:
    factory: Container-Factory
    arguments: ["@file_loader"]

Note that the factory functions are assumed to do something equivalent to the following (return a class instance as the return value):

(defun File-Loader-Factory (your-input-arg)
  "Create an instance of a File-Loader or whatever class."
  (make-instance 'File-Loader :file-name your-input-arg))

Then load up your config file with:

(injection:Container-Factory "/full/path/to/examples/config.yml" :singleton t)

This will create a global singleton instance of the last loaded config that is stored in

*container-singleton*

that you can access parameters and services from with these calls.

Access a parameter within it by using:

(get-parameter "foo") -> "bar"
(get-parameter "an_array") -> '(1 2 3)

Access a service within it by using:

(get-service "file_loader") -> #<FILE-LOADER {CB8A941}>
(get-service "service_container") -> #<CONTAINER {CB7FEE9}>

In case you didn’t notice - yes, this is a sample config that is chaining one container to another different config (you can do that no problem!) - just be careful to avoid a circular loop or you may have an issue.

I will not use global scope Pam I am, I will not in a can

Ok, so, not a fan of global state, I understand.

You can avoid any global state by not specifying the singleton keyword up above, and instead using the package as follows:

(use-package :injection)

(let ((my-container (Container-Factory "example/config.yml")))
           (Container-Get-Parameter my-container "foo")) -> "bar"

Or even simplified to (assuming you only need one value out of there:

(injection:Container-Get-Parameter (injection:Container-Factory "example/config.yml") "foo") -> "bar"

Still on the agenda/caveats

At the moment, the YAML files are read from top to bottom, and this means that you have to write them procedurally if injecting dependencies (which you’re likely to do if you wanted to use this to begin with).

I might also add an optional directory keyword that would allow specifying the location of a package to load (so you could actually load packages or files on your machine through asdf/quicklisp through the YAML configuration, instead of having to do it on your top level asd yourself).

Essentially it would look something like this:

services:
  service_container:
    factory: Container-Factory
    arguments: ["@file_loader"]
    directory: "/path/to/your-project"
    package: "package-name"

and would add the specified directory to your local quicklisp projects directory, followed by a (ql:quickload :package-name), although that may add needless complication.

Installation

Clone the repository (you do know how to git clone by now right?)

Load the system with:

(ql:quickload :injection)
(use-package :injection)

License

GPLv3