Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
This is the first release and contains enough to get started.
  • Loading branch information
elliotchance authored and Elliot Chance committed Jun 6, 2019
0 parents commit 479a901
Show file tree
Hide file tree
Showing 17 changed files with 1,093 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, build with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
/.idea
/dingo
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Elliot Chance

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
143 changes: 143 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# 🐺 dingo

Easy, fast and type-safe dependency injection for Go.

* [Installation](#installation)
* [Building the Container](#building-the-container)
* [Configuring Services](#configuring-services)
* [Using Services](#using-services)
* [Unit Testing](#unit-testing)

## Installation

```bash
go get -u github.com/elliotchance/dingo
```

## Building the Container

Building or rebuilding the container is done with:

```bash
dingo
```

The container is created from a file called `dingo.yml` in the same directory as
where the `dingo` command is run. This should be the root of your
module/repository.

Here is an example of a `dingo.yml`:

```yml
services:
SendEmail:
type: *SendEmail
interface: EmailSender
properties:
From: '"[email protected]"'

CustomerWelcome:
type: *CustomerWelcome
returns: NewCustomerWelcome(@SendEmail)
```
It will generate a file called `dingo.go`. This must be committed with your
code.

## Configuring Services

The `dingo.yml` is described below:

```yml
services:
# Describes each of the services. The name of the service follow the same
# naming conventions as Go, so service names that start with a capital letter
# will be exported (available outside this package).
SendEmail:
# Required: You must provide either 'type' or 'interface'.
# Optional: The type returned by the `return` expression. You must provide a
# fully qualified name that includes the package name if the type does not
# belong to this package. For example:
#
# type: '*github.com/go-redis/redis.Options'
#
type: *SendEmail

# Optional: If you need to replace this service with another struct type in
# unit tests you will need to provide an interface. This will override
# `type` and must be compatible with returned type of `return`.
interface: EmailSender

# Optional: The expression used to instantiate the service. You can provide
# any Go code here, including referencing other services and environment
# variables. Described in more detail below.
returns: NewSendEmail()

# Optional: If 'returns' provides two arguments (where the second one is the
# error) you must include an 'error'. This is the expression when
# "err != nil".
error: panic(err)

# Optional: If provided, a map of case-sensitive properties to be set on the
# instance. Each of the properties is Go code and can have the same
# substitutions described below.
properties:
From: "[email protected]"
maxRetries: 10

# Optional: You can provide explicit imports if you need to reference
# packages in expressions (such as 'returns') that do not exist 'type' or
# 'interface'.
import:
- 'github.com/aws/aws-sdk-go/aws/session'
```
The `returns` and properties can contain any Go code, and allows the following
substitutions:

- `@{SendEmail}` will inject the service named `SendEmail`.
- `${DB_PASS}` will inject the environment variable `DB_PASS`.

## Using Services

As part of the generated file, `dingo.go`. There will be a module-level variable
called `DefaultContainer`. This requires no initialization and can be used
immediately:

```go
func main() {
welcomer := DefaultContainer.GetCustomerWelcome()
err := welcomer.Welcome("Bob", "[email protected]")
// ...
}
```

## Unit Testing

**When unit testing you should not use the global `DefaultContainer`.** You
should create a new container:

```go
container := &Container{}
```

Unit tests can make any modifications to the new container, including overriding
services to provide mocks or other stubs:

```go
func TestCustomerWelcome_Welcome(t *testing.T) {
emailer := FakeEmailSender{}
emailer.On("Send",
"[email protected]", "Welcome", "Hi, Bob!").Return(nil)
container := &Container{}
container.SendEmail = emailer
welcomer := container.GetCustomerWelcome()
err := welcomer.Welcome("Bob", "[email protected]")
assert.NoError(t, err)
emailer.AssertExpectations(t)
}
```
20 changes: 20 additions & 0 deletions dingotest/customer_welcome.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dingotest

import "fmt"

type CustomerWelcome struct {
Emailer EmailSender
}

func NewCustomerWelcome(sender EmailSender) *CustomerWelcome {
return &CustomerWelcome{
Emailer: sender,
}
}

func (welcomer *CustomerWelcome) Welcome(name, email string) error {
body := fmt.Sprintf("Hi, %s!", name)
subject := "Welcome"

return welcomer.Emailer.Send(email, subject, body)
}
90 changes: 90 additions & 0 deletions dingotest/dingo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package dingotest

import (
go_sub_pkg "github.com/elliotchance/dingo/dingotest/go-sub-pkg"
"os"
)

type Container struct {
CustomerWelcome *CustomerWelcome
OtherPkg *go_sub_pkg.Person
OtherPkg2 go_sub_pkg.Greeter
OtherPkg3 *go_sub_pkg.Person
SendEmail EmailSender
SendEmailError *SendEmail
SomeEnv *string
WithEnv1 *SendEmail
WithEnv2 *SendEmail
}

var DefaultContainer = &Container{}

func (container *Container) GetCustomerWelcome() *CustomerWelcome {
if container.CustomerWelcome == nil {
service := NewCustomerWelcome(container.GetSendEmail())
container.CustomerWelcome = service
}
return container.CustomerWelcome
}
func (container *Container) GetOtherPkg() *go_sub_pkg.Person {
if container.OtherPkg == nil {
service := &go_sub_pkg.Person{}
container.OtherPkg = service
}
return container.OtherPkg
}
func (container *Container) GetOtherPkg2() go_sub_pkg.Greeter {
if container.OtherPkg2 == nil {
service := go_sub_pkg.NewPerson()
container.OtherPkg2 = service
}
return container.OtherPkg2
}
func (container *Container) GetOtherPkg3() go_sub_pkg.Person {
if container.OtherPkg3 == nil {
service := go_sub_pkg.Person{}
container.OtherPkg3 = &service
}
return *container.OtherPkg3
}
func (container *Container) GetSendEmail() EmailSender {
if container.SendEmail == nil {
service := &SendEmail{}
service.From = "[email protected]"
container.SendEmail = service
}
return container.SendEmail
}
func (container *Container) GetSendEmailError() *SendEmail {
if container.SendEmailError == nil {
service, err := NewSendEmail()
if err != nil {
panic(err)
}
container.SendEmailError = service
}
return container.SendEmailError
}
func (container *Container) GetSomeEnv() string {
if container.SomeEnv == nil {
service := os.Getenv("ShouldBeSet")
container.SomeEnv = &service
}
return *container.SomeEnv
}
func (container *Container) GetWithEnv1() SendEmail {
if container.WithEnv1 == nil {
service := SendEmail{}
service.From = os.Getenv("ShouldBeSet")
container.WithEnv1 = &service
}
return *container.WithEnv1
}
func (container *Container) GetWithEnv2() *SendEmail {
if container.WithEnv2 == nil {
service := &SendEmail{}
service.From = "foo-" + os.Getenv("ShouldBeSet") + "-bar"
container.WithEnv2 = service
}
return container.WithEnv2
}
40 changes: 40 additions & 0 deletions dingotest/dingo.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
services:
SendEmail:
type: '*SendEmail'
interface: EmailSender
properties:
From: '"[email protected]"'

CustomerWelcome:
type: '*CustomerWelcome'
returns: NewCustomerWelcome(@{SendEmail})

WithEnv1:
type: SendEmail
properties:
From: ${ShouldBeSet}

WithEnv2:
type: '*SendEmail'
properties:
From: '"foo-" + ${ShouldBeSet} + "-bar"'

SomeEnv:
type: string
returns: ${ShouldBeSet}

OtherPkg:
type: '*github.com/elliotchance/dingo/dingotest/go-sub-pkg.Person'

OtherPkg2:
type: '*github.com/elliotchance/dingo/dingotest/go-sub-pkg.Person'
interface: github.com/elliotchance/dingo/dingotest/go-sub-pkg.Greeter
returns: go_sub_pkg.NewPerson()

OtherPkg3:
type: github.com/elliotchance/dingo/dingotest/go-sub-pkg.Person

SendEmailError:
type: '*SendEmail'
returns: NewSendEmail()
error: panic(err)
Loading

0 comments on commit 479a901

Please sign in to comment.