DEV Community

Fernando Correa de Oliveira
Fernando Correa de Oliveira

Posted on • Edited on

Deps

Summary

Deps is a minimalist Raku module for exploring dependency injection patterns through a clear, trait-based API. It offers both low‑level and high‑level interfaces, supports named registrations, trait‑driven function injection, and automatic instantiation of classes, while providing precise control over resolution lifecycles—New, Store, and Scope—and resolution precedence via priority.

1. Motivation and Background

Dependency injection decouples construction of dependencies from their use, improving modularity, testability, and configuration flexibility. Deps serves as an educational playground for understanding inversion of control in Raku and experimenting with DI concepts without the complexity of heavy frameworks.

2. Overview of Deps

Deps exposes:

  • Low‑level API: Manually register and retrieve dependencies by type or name.
  • Block‑scoped DSL: Use deps { ... } to create nested containers and perform inline registrations or imports.
  • Trait‑based injection: Apply is injected to functions/subroutines to have parameters auto‑provided.
  • Automatic instantiation: Construct classes by matching attributes to registered values.

3. Core Features

3.1 Low‑Level API

my Deps $deps .= new;
$deps.register: Bla;
$deps.register:
    -> Int $a, Int :$b --> Str { "$a - $b" },
    :name<value>
;
$deps.register: 13;
$deps.register: 42, :name<a>;
# Resolve
$deps.get(Bla);          # Bla.new(value => "42 - 13", a => 42, b => 13)
$deps.get(Int, :name<a>); # 42
$deps.get(Int);          # 13
Enter fullscreen mode Exit fullscreen mode

3.2 Block‑Scoped DSL

deps {
   injectable C.new: :13attr;
   injected my C $obj;
   # $obj == C.new(attr => 13)
}
Enter fullscreen mode Exit fullscreen mode

3.3 Function‑Level Injection

sub handle-request(UInt $id) is injected {
  injectable UserIdStorage.new: :$id;
  process-request
}
Enter fullscreen mode Exit fullscreen mode

Parameters typed as registered types are injected at call time.

3.4 Automatic Instantiation

class Example { has Int $.x; has Str $.y }
deps {
  injectable 7;
  injectable "hello", :name<y>;
  instantiate(Example);    # Example.new(x => 7, y => "hello")
}
Enter fullscreen mode Exit fullscreen mode

4. Resolution Lifecycles

Deps supports three built‑in lifecycles:

  • New (transient): the default; each call to .get creates a fresh instance.
  • Store (singleton): one instance per container; reused on every .get.
  • Scope (scoped): one instance per deps { ... } block; fresh when entering a new block but reused within it.

Specify with :lifecycle<New|Store|Scope>:

# Transient (default)
$deps.register: -> Int $n --> Int { $n * 2 };

# Singleton
$deps.register: MyService.new, :lifecycle<Store>;

# Scoped
$deps.register: RequestToken.new, :lifecycle<Scope>;
Enter fullscreen mode Exit fullscreen mode

5. Priority

When multiple providers match a type (and optional name), :priority<Strict|Defer> determines which wins.

$deps.register:
    FileLogger.new,
    :lifecycle<Store>,
    :priority<defer>
;
$deps.register:
    ConsoleLogger.new,
    :lifecycle<Store>,
    :priority<strict>
;
# .get(Logger) returns ConsoleLogger
Enter fullscreen mode Exit fullscreen mode

6. Combined Example

role Repo {}
class RealRepo does Repo { has Str $.dsn }
class MockRepo does Repo { }

my Deps $deps .= new;

# Real (singleton)
$deps.register:
    RealRepo.new(
        |(dsn => $_ with %*ENV<DB>)
    ),
    :lifecycle<store>,
    :priority<defer>,
    :name<repo>
;

# Mock (transient)
$deps.register:
    MockRepo,
    :lifecycle<new>,
    :priority<strict>,
    :name<repo>
;

deps-scope :parent($deps), {
  injected my Repo $repo;   # MockRepo wins by priority
}
Enter fullscreen mode Exit fullscreen mode

7. Conclusion and Future Directions

With its lightweight core and idiomatic Raku traits, Deps is ideal for learning DI patterns. Correct lifecycle names—New, Store, and Scope—and built‑in priority give precise control over object lifetimes and resolution. Future enhancements may include circular dependency detection, named scopes, and asynchronous resolution workflows.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more