Skip to content

Commit

Permalink
updating doc and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
ankurs committed Feb 10, 2022
1 parent a1357eb commit 941f724
Show file tree
Hide file tree
Showing 4 changed files with 367 additions and 1 deletion.
198 changes: 197 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "github.com/go-coldbrew/data-builder"

- [Variables](<#variables>)
- [func AddResultToCtx(ctx context.Context, r Result) context.Context](<#func-addresulttoctx>)
- [func BuildGraph(executionPlan Plan, format, file string) error](<#func-buildgraph>)
- [func GetFromResult(ctx context.Context, obj interface{}) interface{}](<#func-getfromresult>)
- [func IsValidBuilder(builder interface{}) error](<#func-isvalidbuilder>)
- [type DataBuilder](<#type-databuilder>)
Expand Down Expand Up @@ -49,6 +50,14 @@ func AddResultToCtx(ctx context.Context, r Result) context.Context

this function should ideally only be used in your tests and/or for debugging modification made to Result obj will NOT persist

## func BuildGraph

```go
func BuildGraph(executionPlan Plan, format, file string) error
```

BuildGraph helps understand the execution plan\, it renders the plan in the given format please note we depend on graphviz\, please ensure you have graphviz installed

## func GetFromResult

```go
Expand Down Expand Up @@ -76,6 +85,153 @@ type DataBuilder interface {
}
```

<details><summary>Example</summary>
<p>

```go
package main

import (
"context"
"fmt"
"strings"
)

// lets say we have some data being produced by a set of functions
// but we need to define how their interaction should be and how their dependency
// should be resolved

type AppRequest struct {
FirstName string
CityName string
UpperCase bool
LowerCase bool
}

type AppResponse struct {
Msg string
}

type NameMsg struct {
Msg string
}

type CityMsg struct {
Msg string
}

type CaseMsg struct {
Msg string
}

// Lets try to build a sample builder with some dependency
// Assuming we have an App that acts on the request
// processes it in multiple steps and returns a Response
// we can think of this process as a series of functions

// NameMsgBuilder builds name salutation from our AppRequest
func NameMsgBuilder(_ context.Context, req AppRequest) (NameMsg, error) {
return NameMsg{
Msg: fmt.Sprintf("Hello %s!", req.FirstName),
}, nil
}

//CityMsgBuilder builds city welcome msg from our AppRequest
func CityMsgBuilder(_ context.Context, req AppRequest) (CityMsg, error) {
return CityMsg{
Msg: fmt.Sprintf("Welcome to %s", req.CityName),
}, nil
}

// CaseMsgBuilder handles the case transformation of the message
func CaseMsgBuilder(_ context.Context, name NameMsg, city CityMsg, req AppRequest) (CaseMsg, error) {
msg := fmt.Sprintf("%s\n%s", name.Msg, city.Msg)
if req.UpperCase {
msg = strings.ToUpper(msg)
} else if req.LowerCase {
msg = strings.ToLower(msg)
}
return CaseMsg{
Msg: msg,
}, nil
}

//ResponseBuilder builds Application response from CaseMsg
func ResponseBuilder(_ context.Context, m CaseMsg) (AppResponse, error) {
return AppResponse{
Msg: m.Msg,
}, nil
}

func main() {
// First we build an object of the builder interface
b := New()

// Then we add all the builders
// its okay to call `AddBuilders` multiple times
err := b.AddBuilders(
NameMsgBuilder,
CityMsgBuilder,
CaseMsgBuilder,
)
fmt.Println(err == nil)

// lets ass all builders
err = b.AddBuilders(ResponseBuilder)
fmt.Println(err == nil)

// next we we compile this into a plan
// the compilation ensures we have a resolved dependency graph
_, err = b.Compile()
fmt.Println(err != nil)

// Why did we get the error ?
// if we look at our dependency graph, there is no builder that produces AppRequest
// in order of dependency resolution to work we need to tell
// the Compile method that we will provide it some initial Data

// we can do that by passing empty structs
// compiler just needs the type, values will come in later
ep, err := b.Compile(AppRequest{})
fmt.Println(err == nil)

// once the Compilation has finished, we get an execution plan
// the execution plan once created can be cached and is side effect free
// It can be executed across multiple go routines
// lets run the Plan, remember to pass in the initial value
result, err := ep.Run(
context.Background(), // context is passed on the builders
AppRequest{
FirstName: "Ankur",
CityName: "Singapore",
LowerCase: true,
},
)
fmt.Println(err == nil)

// once the execution is done, we can read all the values from the result
resp := AppResponse{}
resp = result.Get(resp).(AppResponse)
fmt.Println(resp.Msg)

}
```

#### Output

```
true
true
true
true
true
hello ankur!
welcome to singapore
```

</p>
</details>

### func New

```go
Expand All @@ -88,10 +244,50 @@ New Creates a new DataBuilder

```go
type Plan interface {
Run(context.Context, ...interface{}) (Result, error)
Replace(ctx context.Context, from, to interface{}) error
Run(ctx context.Context, initValues ...interface{}) (Result, error)
}
```

<details><summary>Example</summary>
<p>

```go
{
b := New()
err := b.AddBuilders(DBTestFunc, DBTestFunc4)
fmt.Println(err == nil)
ep, err := b.Compile(TestStruct1{})
fmt.Println(err == nil)

_, err = ep.Run(context.Background(), TestStruct1{})
fmt.Println(err == nil)

err = ep.Replace(context.Background(), DBTestFunc, DBTestFunc5)
fmt.Println(err == nil)
_, err = ep.Run(context.Background(), TestStruct1{})
fmt.Println(err == nil)

}
```

#### Output

```
true
true
CALLED DBTestFunc
CALLED DBTestFunc4
true
true
CALLED DBTestFunc5
CALLED DBTestFunc4
true
```

</p>
</details>

## type Result

```go
Expand Down
134 changes: 134 additions & 0 deletions builder_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package databuilder

import (
"context"
"fmt"
"strings"
)

// lets say we have some data being produced by a set of functions
// but we need to define how their interaction should be and how their dependency
// should be resolved

type AppRequest struct {
FirstName string
CityName string
UpperCase bool
LowerCase bool
}

type AppResponse struct {
Msg string
}

type NameMsg struct {
Msg string
}

type CityMsg struct {
Msg string
}

type CaseMsg struct {
Msg string
}

// Lets try to build a sample builder with some dependency
// Assuming we have an App that acts on the request
// processes it in multiple steps and returns a Response
// we can think of this process as a series of functions

// NameMsgBuilder builds name salutation from our AppRequest
func NameMsgBuilder(_ context.Context, req AppRequest) (NameMsg, error) {
return NameMsg{
Msg: fmt.Sprintf("Hello %s!", req.FirstName),
}, nil
}

//CityMsgBuilder builds city welcome msg from our AppRequest
func CityMsgBuilder(_ context.Context, req AppRequest) (CityMsg, error) {
return CityMsg{
Msg: fmt.Sprintf("Welcome to %s", req.CityName),
}, nil
}

// CaseMsgBuilder handles the case transformation of the message
func CaseMsgBuilder(_ context.Context, name NameMsg, city CityMsg, req AppRequest) (CaseMsg, error) {
msg := fmt.Sprintf("%s\n%s", name.Msg, city.Msg)
if req.UpperCase {
msg = strings.ToUpper(msg)
} else if req.LowerCase {
msg = strings.ToLower(msg)
}
return CaseMsg{
Msg: msg,
}, nil
}

//ResponseBuilder builds Application response from CaseMsg
func ResponseBuilder(_ context.Context, m CaseMsg) (AppResponse, error) {
return AppResponse{
Msg: m.Msg,
}, nil
}

func ExampleDataBuilder() {
// First we build an object of the builder interface
b := New()

// Then we add all the builders
// its okay to call `AddBuilders` multiple times
err := b.AddBuilders(
NameMsgBuilder,
CityMsgBuilder,
CaseMsgBuilder,
)
fmt.Println(err == nil)

// lets ass all builders
err = b.AddBuilders(ResponseBuilder)
fmt.Println(err == nil)

// next we we compile this into a plan
// the compilation ensures we have a resolved dependency graph
_, err = b.Compile()
fmt.Println(err != nil)

// Why did we get the error ?
// if we look at our dependency graph, there is no builder that produces AppRequest
// in order of dependency resolution to work we need to tell
// the Compile method that we will provide it some initial Data

// we can do that by passing empty structs
// compiler just needs the type, values will come in later
ep, err := b.Compile(AppRequest{})
fmt.Println(err == nil)

// once the Compilation has finished, we get an execution plan
// the execution plan once created can be cached and is side effect free
// It can be executed across multiple go routines
// lets run the Plan, remember to pass in the initial value
result, err := ep.Run(
context.Background(), // context is passed on the builders
AppRequest{
FirstName: "Ankur",
CityName: "Singapore",
LowerCase: true,
},
)
fmt.Println(err == nil)

// once the execution is done, we can read all the values from the result
resp := AppResponse{}
resp = result.Get(resp).(AppResponse)
fmt.Println(resp.Msg)

//Output:
// true
// true
// true
// true
// true
// hello ankur!
// welcome to singapore
}
8 changes: 8 additions & 0 deletions plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ func (r Result) Get(obj interface{}) interface{} {
}

func (p plan) BuildGraph(format, file string) error {
const (
FNCOLOR = "red"
STRUCTCOLOR = "blue"
)

g := graphviz.New()
graph, err := g.Graph(graphviz.Name("Dependency Graph"))
if err != nil {
Expand All @@ -132,16 +137,19 @@ func (p plan) BuildGraph(format, file string) error {
if err != nil {
return err
}
fn = fn.SetFontColor(FNCOLOR)
out, err := graph.CreateNode(b.Out)
if err != nil {
return err
}
out = out.SetFontColor(STRUCTCOLOR)
graph.CreateEdge("Out", fn, out)
for _, in := range b.In {
in, err := graph.CreateNode(in)
if err != nil {
return err
}
in = in.SetFontColor(STRUCTCOLOR)
graph.CreateEdge("In", in, fn)
}
}
Expand Down
Loading

0 comments on commit 941f724

Please sign in to comment.