Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rwnx/hardwire
A compile-time, non-intrusive dependency injection system.
https://github.com/rwnx/hardwire
crystal dependency-injection hacktoberfest macros
Last synced: 3 months ago
JSON representation
A compile-time, non-intrusive dependency injection system.
- Host: GitHub
- URL: https://github.com/rwnx/hardwire
- Owner: rwnx
- License: mit
- Created: 2020-01-20T00:38:39.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2024-04-15T10:08:17.000Z (7 months ago)
- Last Synced: 2024-05-19T05:30:09.696Z (6 months ago)
- Topics: crystal, dependency-injection, hacktoberfest, macros
- Language: Crystal
- Homepage: https://rwnx.github.io/hardwire/
- Size: 128 KB
- Stars: 21
- Watchers: 2
- Forks: 1
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-crystal - HardWire - A compile-time non-intrusive dependency injection system (Dependency Injection)
README
# HardWire ⚡
[![Crystal CI](https://github.com/rwnx/hardwire/workflows/Crystal%20CI/badge.svg?branch=main)](https://github.com/rwnx/hardwire/actions?query=workflow%3A%22Crystal+CI%22)A Compile-time Dependency Injection system for Crystal.
## Installation
1. Add the dependency to your `shard.yml`:
```yaml
dependencies:
hardwire:
github: rwnx/hardwire
```2. Run `shards install`
## Usage
```crystal
require "hardwire"
```Hardwire is designed to operate inside a container object.
Since the resolution is compile-time (Using Macros), normally this will be a module.### Creating a container 📦
```crystal
# To create a new container, include `HardWire::Container`
# This will add the macros you need to register and resolve wiring
module Container
include HardWire::Container# use transient/singleton to wire different lifecycles
# singleton dependencies will be memoized
# dependencies for the constructor will be resolved from the constructor automatically
transient Dependency
singleton NeedsDependency
scoped Webservice# you can also register dependencies with a block instead of inspecting the constructor
# Your block MUST return an instance of the class you are registering
singleton NeedsDependency {
NeedsDependency.new( self.resolve Dependency )
}
end
```Hardwire tries to operate with minimal modifications to other classes (unless required).
_"simple"_ classes, e.g.
* Have a single constructor
* Have unique dependencies/do not require tagsIf your classes match this signature, you can wire up in the container without adding anything to the classes.
For everything else, there's:
### Multiple Constructors 🚧
Hardwire needs to know which constuctor function to use.Annotate your "Injectable" constructor with the Hardwire::Inject annotation.
```crystal
class MultipleInits
@[HardWire::Inject]
def initialize(input: String)
# register will inspect this method's arguments
# [...]
enddef initialize
# will not be used for injection
# [...]
end
end
```### Tags 🏷
To differentiate between registrations of _the same type_, use the HardWire::Tags annotation.
Tags allow you to attach additional metadata to the signature. Tags themselves are string-based, simple identifiers (/\w+/) that allow you to resolve
a different registration of the same class.```crystal
# [...]# registering a transient dependency with tag "secret"
transient String, "secret" {
"a secret string"
}# registering a singleton
# When no tags are set, it is considered the "default" registration
singleton DbService# registering a different singleton with a tag
singleton DbService, "primary"# Resolving Dependencies
class Resolving
@[Hardwire::Tags(input: "secret", primary_db: "primary")]
def initialize(input : String, primary_db : DbService, default_db : DbService)
end
end
```
### Lifecycles ♽
There are 3 lifecycles available for registrations:
* Singleton: The dependency is instantiated once for the lifetime of the application
* Scoped: the dependency instantiated once for each created scope and destroyed when the scope is garbage-collected
* Transient: the dependency is instatiated each time it is resolved#### Scopes 🔭
To managed scoped instances, you should create a scope object with the `.scope` macro.```crystal
# This example will init a database DatabaseConnection for each http request
# but all the databases will recieve the same instance of config (singleton)
# the ScopedLogging dependency will also be instantiated once for each scope resolution
require "kemal"
class Config; end
class ScopedLogging; end
class DatabaseConnection
def initialize(@config : Config, @logging : ScopedLogging)
end
endmodule Container
include HardWire::Containersingleton Config
scoped ScopedLogging
scoped DatabaseConnection
endget "/" do
# create a unique scope
scope = Container.scopelogger = scope.resolve ScopedLogging
db = scope.resolve DatabaseConnection
db.get_some_datalogger.log("I share a logger with the database in scope!")
endKemal.run
```
### Resolving Manually 🔨
You can resolve dependencies manually using the `.resolve` macro. This allows you to resolve dependencies manually with the tag string.```crystal
module Container
include HardWire::Containertransient SecretService, "primary"
singleton DatabaseThing
endservice = Container.resolve SecretService, "primary"
db = Container.resolve DatabaseThing
```### Runtime Interrogation 👀
Hardwire can tell you information about the registrations at runtime, but the dependencies are _HardWired_ (See what I did there?), so they can't be changed.```crystal
module Container
include HardWire::Containersingleton DbService
endContainer.registered?(DbService) # true
Container.registered?(DbService, "tagged") # false
Container.registered?(String) # false
```## Contributing
1. Fork it ()
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request## Contributors
- [rwnx](https://github.com/rwnx) - creator and maintainer