Skip to content

Commit

Permalink
Improve docs (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim Bovtunov committed May 1, 2023
1 parent 9c21297 commit 10db8d6
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 215 deletions.
45 changes: 21 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
DI
===
# DI

[![Documentation](https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/goava/di)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/goava/di?logo=semver&style=for-the-badge)](https://github.com/goava/di/releases/latest)
[![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/goava/di/go.yml?branch=master&logo=github-actions&style=for-the-badge)](https://github.com/goava/di/actions/workflows/go.yml)
[![Go Report Card](https://img.shields.io/badge/go%20report-A%2B-green?style=for-the-badge)](https://goreportcard.com/report/github.com/goava/di)
[![Codecov](https://img.shields.io/codecov/c/github/goava/di?logo=codecov&style=for-the-badge)](https://codecov.io/gh/goava/di)

Dependency injection for Go programming language.
**DI** is a dependency injection library for the Go programming language.

[Tutorial](./docs/tutorial.md) | [Examples](./_examples) |
[Advanced features](./docs/advanced.md)

Dependency injection is one form of the broader technique of inversion
of control. It is used to increase modularity of the program and make it
extensible.

This library helps you to organize responsibilities in your codebase and
make it easy to combine low-level implementation into high-level
behavior without boilerplate.
Dependency injection is a form of inversion of control that increases modularity and extensibility in your programs.
This library helps you organize responsibilities in your codebase and makes it easy to combine low-level implementations
into high-level behavior without boilerplate.

## Features

Expand All @@ -30,25 +22,30 @@ behavior without boilerplate.
- Lazy-loading
- Tagging
- Grouping
- Iteration
- Decoration
- Cleanup
- Container Chaining / Scopes

## Documentation

You can use standard
[pkg.go.dev](https://pkg.go.dev/github.com/goava/di) and inline code
comments. If you do not have experience with auto-wiring libraries as
[google/wire](https://github.com/google/wire),
[uber-go/dig](https://github.com/uber-go/dig) or another - start with
[tutorial](./docs/tutorial.md).

## Install
## Installation

```shell
go get github.com/goava/di
```

## What it looks like
## Documentation

You can use the standard [pkg.go.dev](https://pkg.go.dev/github.com/goava/di) and inline code comments. If you are new
to auto-wiring libraries such as [google/wire](https://github.com/google/wire)
or [uber-go/dig](https://github.com/uber-go/dig), start with the [tutorial](./docs/tutorial.md).

### Essential Reading

- [Tutorial](./docs/tutorial.md)
- [Examples](./_examples)
- [Advanced Features](./docs/advanced.md)

## Example Usage

```go
package main
Expand Down
58 changes: 29 additions & 29 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,28 @@ type Container struct {
//
// Define constructors and invocations:
//
// func NewHTTPServer(mux *http.ServeMux) *http.Server {
// return &http.Server{
// Handler: mux,
// }
// }
// func NewHTTPServer(mux *http.ServeMux) *http.Server {
// return &http.Server{
// Handler: mux,
// }
// }
//
// func NewHTTPServeMux() *http.ServeMux {
// return http.ServeMux{}
// }
// func NewHTTPServeMux() *http.ServeMux {
// return http.ServeMux{}
// }
//
// func StartServer(server *http.Server) error {
// func StartServer(server *http.Server) error {
// return server.ListenAndServe()
// }
//
// Use it with container:
//
// container, err := di.New(
// di.Provide(NewHTTPServer),
// di.Provide(NewHTTPServeMux),
// container, err := di.New(
// di.Provide(NewHTTPServer),
// di.Provide(NewHTTPServeMux),
// di.Invoke(StartServer),
// )
// if err != nil {
// )
// if err != nil {
// // handle error
// }
func New(options ...Option) (_ *Container, err error) {
Expand All @@ -62,7 +62,7 @@ func New(options ...Option) (_ *Container, err error) {

// Apply applies options to container.
//
// err := container.Apply(
// err := container.Apply(
// di.Provide(NewHTTPServer),
// )
// if err != nil {
Expand Down Expand Up @@ -96,9 +96,9 @@ func (c *Container) ProvideValue(value Value, options ...ProvideOption) error {

// Invocation is a function whose signature looks like:
//
// func StartServer(server *http.Server) error {
// return server.ListenAndServe()
// }
// func StartServer(server *http.Server) error {
// return server.ListenAndServe()
// }
//
// Like a constructor invocation may have unlimited count of arguments and
// they will be resolved automatically. The invocation can return an optional error.
Expand All @@ -122,7 +122,7 @@ type Pointer interface{}

// Has checks that type exists in container, if not it return false.
//
// var server *http.Server
// var server *http.Server
// if container.Has(&server) {
// // handle server existence
// }
Expand Down Expand Up @@ -158,16 +158,16 @@ type IterateFunc func(tags Tags, value ValueFunc) error

// Iterate iterates over group of Pointer type with IterateFunc.
//
// var servers []*http.Server
// iterFn := func(tags di.Tags, loader ValueFunc) error {
// i, err := loader()
// if err != nil {
// return err
// }
// // do stuff with result: i.(*http.Server)
// return nil
// }
// container.Iterate(&servers, iterFn)
// var servers []*http.Server
// iterFn := func(tags di.Tags, loader ValueFunc) error {
// i, err := loader()
// if err != nil {
// return err
// }
// // do stuff with result: i.(*http.Server)
// return nil
// }
// container.Iterate(&servers, iterFn)
func (c *Container) Iterate(target Pointer, fn IterateFunc, options ...ResolveOption) error {
node, err := c.find(target, options...)
if err != nil {
Expand Down
112 changes: 86 additions & 26 deletions docs/advanced.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# Advanced features
# Advanced Features

- [Modules](#modules)
- [Tags](#tags)
- [Optional parameters](#optional-parameters)
- [Struct fields injection](#struct-fields-injection)
- [Optional Parameters](#optional-parameters)
- [Struct Field Injection](#struct-field-injection)
- [Iteration](#iteration)
- [Decoration](#decoration)
- [Cleanup](#cleanup)
- [Container Chaining / Scopes](#container-chaining--scopes)

### Modules

You can group previous options into single variable by using
`di.Options()`:
You can group previous options into a single variable using `di.Options()`:

```go
// account module
Expand All @@ -36,8 +36,8 @@ if err != nil {

### Tags

If you have more than one instances of same type, you can specify alias.
For example two instances of database: leader - for writing, follower -
If you have more than one instance of the same type, you can specify an alias.
For example, two instances of a database: leader - for writing, follower -
for reading.

#### Wrap type into another unique type
Expand All @@ -63,15 +63,15 @@ di.Provide(NewLeader, di.Tags{"type":"leader"})
di.Provide(NewFollower, di.Tags{"type", "follower"}))
```

If you need to resolve it from the container use `di.Tags` *resolve
If you need to resolve it from the container, use `di.Tags` *resolve
option*.

```go
var db *Database
container.Resolve(&db, di.Tags{"type": "leader"}))
```

If you need to provide named definition in another constructor embed
If you need to provide a named definition in another constructor, embed
`di.Inject`.

```go
Expand All @@ -84,7 +84,7 @@ type Parameters struct {
Follower *Database `di:"type=follower"`
}

// NewService creates new service with provided parameters.
// NewService creates a new service with provided parameters.
func NewService(parameters Parameters) *Service {
return &Service{
Leader: parameters.Leader,
Expand All @@ -93,18 +93,18 @@ func NewService(parameters Parameters) *Service {
}
```

If you need to resolve all types with same tag key, use `*` as tag
If you need to resolve all types with the same tag key, use `*` as the tag
value:

```go
var db []*Database
di.Resolve(&db, di.Tags{"type": "*"})
```

### Optional parameters
### Optional Parameters

Also, `di.Inject` with tag `di:"optional"` provide ability to skip dependency
if it not exists in the container.
Also, `di.Inject` with tag `di:"optional"` provides the ability to skip a dependency
if it does not exist in the container.

```go
// ServiceParameter
Expand All @@ -130,7 +130,7 @@ type ServiceParameter struct {
}
```

If you need to skip fields injection use `di:"skip"` tags for this:
If you need to skip field injection, use `di:"skip"` tags for this:

```go
// ServiceParameter
Expand All @@ -143,11 +143,11 @@ type ServiceParameter struct {
}
```

### Struct fields injection
### Struct Field Injection

To avoid constant constructor changes, you can use `di.Inject`. Only
struct pointers are supported as constructing result. And only
`di`-taged fields will be injected. Such a constructor will work with
struct pointers are supported as constructing results. And only
`di`-tagged fields will be injected. Such a constructor will work with
using `di` only.

```go
Expand All @@ -161,19 +161,80 @@ type Controller struct {
Friends FriendsService `di:"type=cached"`
}

// NewController creates controller.
// NewController creates a controller.
func NewController() *Controller {
return &Controller{}
}
```

### Iteration

TBD
The `di` package provides iteration capabilities, allowing you to iterate over a group of a specific Pointer type with the `IterateFunc`. This can be useful when working with multiple instances of a type or when you need to perform actions on each instance.

```go
// ValueFunc is a lazy-loading wrapper for iteration.
type ValueFunc func() (interface{}, error)

// IterateFunc is a function that will be called on each instance in the iterate selection.
type IterateFunc func(tags Tags, value ValueFunc) error
```

To use iteration with the container, follow the example below:

```go
var servers []*http.Server
iterFn := func(tags di.Tags, loader ValueFunc) error {
i, err := loader()
if err != nil {
return err
}
// do stuff with result: i.(*http.Server)
return nil
}

container.Iterate(&servers, iterFn)
```

In this example, the `Iterate` method is called on the container, passing a slice of pointers to the desired type (in this case, `*http.Server`) and the iterate function, which will be executed on each instance.

### Decoration

TBD
The `di` package supports decoration, allowing you to modify container instances through the use of decorators. This can be helpful when you need to make additional modifications to instances after they have been constructed.

```go
// Decorator can modify container instance.
type Decorator func(value Value) error

// Decorate will be called after type construction. You can modify your pointer types.
func Decorate(decorators ...Decorator) ProvideOption {
return provideOption(func(params *ProvideParams) {
params.Decorators = append(params.Decorators, decorators...)
})
}
```

To use decorators, you can add them to the `Provide` method using the `Decorate` function. Here's an example of a decorator that logs the creation of each instance:

```go
// Logger is a simple logger interface for demonstration purposes
type Logger interface {
Log(message string)
}

// logInstanceCreation is a decorator that logs the creation of instances
func logInstanceCreation(logger Logger) Decorator {
return func(value Value) error {
logger.Log(fmt.Sprintf("Instance of type logger created"))
return nil
}
}

// Usage example
container, err := di.New(
di.Provide(NewMyType, di.Decorate(logInstanceCreation(myLogger))),
)
```

In this example, the `logInstanceCreation` decorator logs a message every time a new instance is created. The decorator is added to the `Provide` method using the `Decorate` function, and it is executed after the type construction.

### Cleanup

Expand Down Expand Up @@ -213,8 +274,8 @@ container.Cleanup() // file was closed
### Container Chaining / Scopes

You can chain containers together so that values can be resolved from a
parent container. This lets you do things like have a configuration
scope container and an application scoped container. By keeping
parent container. This lets you do things like have a configuration
scope container and an application scoped container. By keeping
configuration values in a different container, you can re-create
the application scoped container when you make configuration changes
since each container has an independent lifecycle.
Expand All @@ -237,5 +298,4 @@ if err := appContainer.AddParent(configContainer); err != nil {

var server *http.Server
err := appContainer.Resolve(&server)
```

```
Loading

0 comments on commit 10db8d6

Please sign in to comment.