diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ab6648a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,36 @@
+## Intellij
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/encodings.xml
+.idea/**/compiler.xml
+.idea/**/misc.xml
+.idea/**/modules.xml
+.idea/**/vcs.xml
+
+## VSCode
+.vscode/
+
+## File-based project format:
+*.iws
+*.iml
+.idea/
+
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+*.dat
+*.DS_Store
+go.sum
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Goreleaser builds
+dist/**
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..c1d8e80
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Luca Sepe
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2c3cf8e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,261 @@
+# Draft
+
+A commandline tool that generate **H**igh **L**evel microservice & serverless **A**rchitecture diagrams using a declarative syntax defined in a YAML file.
+
+- Works on Linux, Mac OSX, Windows
+- Just a single portable binary file
+- It Does One Thing Well
+- Input data in flat YAML text files
+- Usable with shell scripts
+- Can take input from pipes `cat`
+
+## How `draft` works?
+
+`draft` takes in input a declarative YAML file and generates a [`dot`](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) script for [Graphviz](https://www.graphviz.org/)
+
+```bash
+draft backend-for-frontend.yml | dot -Tpng -Gdpi=200 > backend-for-frontend.png
+```
+
+Piping the `draft` output to [GraphViz](http://www.graphviz.org/doc/info/output.html/) `dot` you can generate the following output formats:
+
+| format | command |
+|:-------------|:---------------------------------------------------------------|
+| GIF | draft input.yml | dot -Tgif > output.gif
|
+| JPEG | draft input.yml | dot -Tjpg > output.jpg
|
+| PostScript | draft input.yml | dot -Tps > output.ps
|
+| PSD | draft input.yml | dot -Tpsd > output.psd
|
+| SVG | draft input.yml | dot -Tsvg > output.svg
|
+| WebP | draft input.yml | dot -Twebp > output.webp
|
+
+To install GraphViz to your favorite OS, please, follow this link [https://graphviz.gitlab.io/download/](https://graphviz.gitlab.io/download/).
+
+## Components
+
+### A picture is worth a thousand words
+
+... and this is particularly true in regard to complex IT architectures.
+
+The basic unit of each _draft_ design is the `component`:
+
+```go
+type Component struct {
+ ID string `yaml:"id,omitempty"` // optional - autogenerated if omitted (read more for details...)
+ Kind string `yaml:"kind"` // required (one of: service, gateway, queue, broker, function, storage, database)
+ Label string `yaml:"label,omitempty"` // optional - the component description (or scope)
+ Provider string `yaml:"provider,omitempty"` // optional - you can use this to specify the implementation
+ FillColor string `yaml:"fillColor,omitempty"` // optional - the hex code for the background color
+ FontColor string `yaml:"fontColor,omitempty"` // optional - the hex code for the foreground color
+ Rounded bool `yaml:"rounded,omitempty"` // optional - set to true if you wants rounded shapes
+}
+```
+
+Draft uses a set of symbols independent from the different providers (AWS, Microsoft Azure, GCP).
+
+- you can eventually describe the implementation using the `provider` attribute.
+
+Below is a list of all the components currently implemented.
+
+| Component | Kind | YAML | Output |
+|:-------------------|:------------|:--------------------------|:--------------------------------:|
+| **Client** | `client` | ![](./examples/cl.jpg) | ![](./examples/client.png) |
+| **Microservice** | `service` | ![](./examples/ms.jpg) | ![](./examples/service.png) |
+| **Gateway** | `gateway` | ![](./examples/gt.jpg) | ![](./examples/gateway.png) |
+| **Message Broker** | `broker` | ![](./examples/br.jpg) | ![](./examples/broker.png) |
+| **Queue Service** | `queue` | ![](./examples/qs.jpg) | ![](./examples/queue.png) |
+| **Object Storage** | `storage` | ![](./examples/st.jpg) | ![](./examples/storage.png) |
+| **Function** | `function` | ![](./examples/fn.jpg) | ![](./examples/function.png) |
+| **Database** | `database` | ![](./examples/db.jpg) | ![](./examples/database.png) |
+
+## Connections
+
+You can connect each component by arrows.
+
+To be able to connect an _origin component_ with one or more _target component_ you need to specify each `componentId`.
+
+- you can define your component `id` explicitly
+- you can omit the component `id` attribute and it will be autogenerated
+
+### Autogenerated `id`
+
+An autogenerated `id` has a prefix and a sequential number
+
+- the prefix is related to the component `kind`
+
+Aautogenerated `id` prefix mapping.
+
+| a kind of... | will generate an `id` prefix with... | examples |
+|:-------------|:-------------------------------------|:---------------|
+| `client` | `cl` | `cl1, cl2,...` |
+| `service` | `ms` | `ms1, ms2,...` |
+| `gateway` | `gt` | `gt1, gt2,...` |
+| `broker` | `br` | `br1, br2,...` |
+| `queue` | `qs` | `qs1, qs2,...` |
+| `storage` | `st` | `st1, st2,...` |
+| `function` | `fn` | `fn1, fn2,...` |
+| `database` | `db` | `db1, db2,...` |
+
+A `connection` has the following properties:
+
+```go
+type Connection struct {
+ Origin struct {
+ ComponentID string `yaml:"componentId"`
+ } `yaml:"origin"`
+ Targets []struct {
+ ComponentID string `yaml:"componentId"`
+ Label string `yaml:"label,omitempty"`
+ Color string `yaml:"color,omitempty"`
+ Dashed bool `yaml:"dashed,omitempty"`
+ Dir string `yaml:"dir,omitempty"`
+ Highlight bool `yaml:"highlight,omitempty"`
+ } `yaml:"targets"`
+}
+```
+
+## Example 1 - Message Bus Pattern
+
+Create the `draft` architecture descriptor YAML with your favorite editor:
+
+```yaml
+title: message bus pattern
+backgroundColor: '#ffffff'
+components:
+ -
+ kind: service
+ label: Producer
+ provider: AWS EC2
+ -
+ kind: broker
+ label: "Notification\nService"
+ provider: AWS SNS
+ -
+ kind: queue
+ label: "event queue @ topic 1"
+ provider: AWS SQS
+ -
+ kind: queue
+ label: "event queue @ topic 2"
+ provider: AWS SQS
+ -
+ kind: service
+ label: "Consumer\n@ topic 1"
+ provider: AWS EC2
+ -
+ kind: service
+ label: "Consumer\n@ topic 2"
+ provider: AWS EC2
+connections:
+ -
+ origin:
+ componentId: ms1
+ targets:
+ -
+ componentId: br1
+ -
+ origin:
+ componentId: br1
+ targets:
+ -
+ componentId: qs1
+ dashed: true
+ -
+ componentId: qs2
+ dashed: true
+ -
+ origin:
+ componentId: qs1
+ targets:
+ -
+ componentId: ms2
+ dir: back
+ -
+ origin:
+ componentId: qs2
+ targets:
+ -
+ componentId: ms3
+ dir: back
+```
+
+Then run `draft`:
+
+```bash
+draft message-bus-pattern.yml | dot -Tpng > message-bus-pattern.png
+```
+
+Here the generated output:
+
+![](./examples/message-bus-pattern.png)
+
+
+## Example 2 - AWS Cognito Custom Authentication Flow
+
+Create the draft architecture descriptor YAML with your favorite editor:
+
+```yaml
+title: Amazon Cognito Custom Authentication Flow with external database
+backgroundColor: '#ffffff'
+components:
+ -
+ kind: client
+ label: "Web App"
+ -
+ kind: client
+ label: "Mobile App"
+ -
+ kind: service
+ label: "Cognito"
+ provider: "AWS Cognito"
+ fillColor: '#991919'
+ fontColor: '#fafafa'
+ -
+ kind: function
+ label: "Define\nAuthChallange"
+ provider: "AWS Lambda"
+ -
+ kind: function
+ label: "Create\nAuthChallange"
+ provider: "AWS Lambda"
+ -
+ kind: function
+ label: "Verify\nAuthChallange"
+ provider: "AWS Lambda"
+ -
+ kind: database
+ label: "Users\nRepository"
+ provider: "AWS RDS"
+connections:
+ -
+ origin:
+ componentId: cl1
+ targets:
+ -
+ componentId: ms1
+ -
+ origin:
+ componentId: cl2
+ targets:
+ -
+ componentId: ms1
+ -
+ origin:
+ componentId: ms1
+ targets:
+ -
+ componentId: fn1
+ -
+ componentId: fn2
+ -
+ componentId: fn3
+ -
+ origin:
+ componentId: fn2
+ targets:
+ -
+ componentId: db1
+```
+
+Here the generated output:
+
+![](./examples/aws-cognito-custom-auth-flow.png)
\ No newline at end of file
diff --git a/broker.go b/broker.go
new file mode 100644
index 0000000..41a1656
--- /dev/null
+++ b/broker.go
@@ -0,0 +1,54 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type broker struct {
+ seq int16
+}
+
+func (rcv *broker) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("br%d", rcv.seq)
+}
+
+func (rcv *broker) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ label := comp.Label
+ if strings.TrimSpace(comp.Label) == "" {
+ label = "Message Broker"
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor(comp.FillColor, "#e0eeeeff"),
+ node.Shape("cds"),
+ )
+ el.Attr("height", "0.8")
+}
+
+/** Alternative
+
+label=
+ topic 1 |
+ topic 2 |
+ ... |
+ topic N |
+
>
+shape="plain"
+
+**/
diff --git a/client.go b/client.go
new file mode 100644
index 0000000..3e68abb
--- /dev/null
+++ b/client.go
@@ -0,0 +1,38 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type client struct {
+ seq int16
+}
+
+func (rcv *client) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("cl%d", rcv.seq)
+}
+
+func (rcv *client) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(comp.Label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor(comp.FillColor, "#90ee90ff"),
+ node.Shape("underline"),
+ )
+ el.Attr("fontsize", "8")
+ el.Attr("height", "0.3")
+}
diff --git a/cmd/.gitignore b/cmd/.gitignore
new file mode 100644
index 0000000..3c46005
--- /dev/null
+++ b/cmd/.gitignore
@@ -0,0 +1,38 @@
+## Intellij
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/encodings.xml
+.idea/**/compiler.xml
+.idea/**/misc.xml
+.idea/**/modules.xml
+.idea/**/vcs.xml
+
+## File-based project format:
+*.iws
+*.iml
+.idea/
+
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+*.dat
+*.DS_Store
+go.sum
+
+# Test binary, built with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Goreleaser builds
+dist/**
+
+# Goreleaser file
+.goreleaser.yml
+
+# Custom Bash Scripts
+draft-all.sh
diff --git a/cmd/main.go b/cmd/main.go
new file mode 100644
index 0000000..0ccee21
--- /dev/null
+++ b/cmd/main.go
@@ -0,0 +1,95 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/lucasepe/draft"
+)
+
+// go run main.go | dot -Tpng -Gdpi=300 > test.png
+
+const (
+ maxFileSize = 500 * 1024
+ banner = `
+______ __ _
+| _ \ / _|| | Crafted with passion by Luca Sepe
+| | | | _ __ __ _ | |_ | |_
+| | | || '__| / _' || _|| __| https://github.com/lucasepe/draft
+| |/ / | | | (_| || | | |_
+|___/ |_| \__,_||_| \__| {{VERSION}}`
+)
+
+var (
+ version = "dev"
+ commit = "none"
+ date = "unknown"
+)
+
+func main() {
+ configureFlags()
+
+ if flag.CommandLine.Arg(0) == "" {
+ flag.CommandLine.Usage()
+ os.Exit(2)
+ }
+
+ fn, err := filepath.Abs(flag.Args()[0])
+ handleErr(err)
+
+ file, err := os.Open(fn)
+ handleErr(err)
+
+ defer file.Close()
+
+ ark, err := draft.NewDraft(file)
+ handleErr(err)
+
+ str, err := ark.Sketch()
+ handleErr(err)
+
+ fmt.Println(str)
+}
+
+// handleErr check for an error and eventually exit
+func handleErr(err error) {
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
+ os.Exit(1)
+ }
+}
+
+func configureFlags() {
+ flag.CommandLine.Usage = func() {
+ printBanner()
+ fmt.Printf("Generate High Level Microservice Architecture diagrams for GraphViz using simple YAML syntax.\n\n")
+
+ name := filepath.Base(os.Args[0])
+
+ fmt.Print("USAGE:\n\n")
+ fmt.Printf(" %s [options] /path/to/yaml/file\n\n", name)
+
+ fmt.Print("EXAMPLE:\n\n")
+ fmt.Printf(" %s input.yml | dot -Tpng -Gdpi=200 > output.png\n\n", name)
+
+ fmt.Print("OPTIONS:\n\n")
+ flag.CommandLine.SetOutput(os.Stdout)
+ flag.CommandLine.PrintDefaults()
+ flag.CommandLine.SetOutput(ioutil.Discard) // hide flag errors
+ fmt.Print(" -help\n\tprints this message\n")
+ fmt.Println()
+ }
+
+ flag.CommandLine.SetOutput(ioutil.Discard) // hide flag errors
+ flag.CommandLine.Init(os.Args[0], flag.ExitOnError)
+
+ flag.CommandLine.Parse(os.Args[1:])
+}
+
+func printBanner() {
+ fmt.Print(strings.Trim(strings.Replace(banner, "{{VERSION}}", version, 1), "\n"), "\n\n")
+}
diff --git a/database.go b/database.go
new file mode 100644
index 0000000..42990e8
--- /dev/null
+++ b/database.go
@@ -0,0 +1,38 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type database struct {
+ seq int16
+}
+
+func (rcv *database) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("db%d", rcv.seq)
+}
+
+func (rcv *database) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(comp.Label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor(comp.FillColor, "#f5f5dcff"),
+ node.Shape("cylinder"),
+ )
+ el.Attr("height", "0.5")
+ el.Attr("fontsize", "6")
+}
diff --git a/draft.go b/draft.go
new file mode 100644
index 0000000..887d87f
--- /dev/null
+++ b/draft.go
@@ -0,0 +1,137 @@
+package draft
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/edge"
+ "github.com/lucasepe/draft/pkg/graph"
+ "gopkg.in/yaml.v2"
+)
+
+const (
+ kindClient = "client"
+ kindGateway = "gateway"
+ kindService = "service"
+ kindQueue = "queue"
+ kindBroker = "broker"
+ kindStorage = "storage"
+ kindDatabase = "database"
+ kindFunction = "function"
+)
+
+// Connection is a link between two components.
+type Connection struct {
+ Origin struct {
+ ComponentID string `yaml:"componentId"`
+ } `yaml:"origin"`
+ Targets []struct {
+ ComponentID string `yaml:"componentId"`
+ Label string `yaml:"label,omitempty"`
+ Color string `yaml:"color,omitempty"`
+ Dashed bool `yaml:"dashed,omitempty"`
+ Dir string `yaml:"dir,omitempty"`
+ Highlight bool `yaml:"highlight,omitempty"`
+ } `yaml:"targets"`
+}
+
+// Component is a basic architecture unit.
+type Component struct {
+ ID string `yaml:"id,omitempty"`
+ Kind string `yaml:"kind"`
+ Label string `yaml:"label,omitempty"`
+ Provider string `yaml:"provider,omitempty"`
+ FillColor string `yaml:"fillColor,omitempty"`
+ FontColor string `yaml:"fontColor,omitempty"`
+ Rounded bool `yaml:"rounded,omitempty"`
+}
+
+// Draft represents a whole diagram.
+type Draft struct {
+ Title string `yaml:"title,omitempty"`
+ BackgroundColor string `yaml:"backgroundColor,omitempty"`
+ Components []Component `yaml:"components"`
+ Connections []Connection `yaml:"connections,omitempty"`
+
+ sketchers map[string]interface {
+ sketch(*dot.Graph, Component)
+ }
+}
+
+// NewDraft returns a new decoded Draft struct
+func NewDraft(r io.Reader) (*Draft, error) {
+ res := &Draft{
+ sketchers: map[string]interface {
+ sketch(*dot.Graph, Component)
+ }{
+ kindClient: &client{},
+ kindGateway: &gateway{},
+ kindService: &service{},
+ kindBroker: &broker{},
+ kindQueue: &queue{},
+ kindFunction: &function{},
+ kindStorage: &storage{},
+ kindDatabase: &database{},
+ },
+ }
+
+ // Init new YAML decode
+ d := yaml.NewDecoder(r)
+
+ // Start YAML decoding from file
+ if err := d.Decode(&res); err != nil {
+ return nil, err
+ }
+
+ return res, nil
+}
+
+// Sketch generates the GraphViz definition for this architecture diagram.
+func (ark *Draft) Sketch() (string, error) {
+ g := graph.New(graph.BackgroundColor(ark.BackgroundColor), graph.Label(ark.Title))
+
+ if err := sketchComponents(g, ark); err != nil {
+ return "", err
+ }
+
+ if err := sketchConnections(g, ark); err != nil {
+ return "", err
+ }
+
+ return g.String(), nil
+}
+
+func sketchComponents(graph *dot.Graph, draft *Draft) error {
+ for _, el := range draft.Components {
+ sketcher, ok := draft.sketchers[el.Kind]
+ if !ok {
+ return fmt.Errorf("render not found for component of kind '%s'", el.Kind)
+ }
+
+ sketcher.sketch(graph, el)
+ }
+
+ return nil
+}
+
+func sketchConnections(graph *dot.Graph, draft *Draft) error {
+ for _, el := range draft.Connections {
+ var from = el.Origin.ComponentID
+
+ for _, x := range el.Targets {
+ err := edge.New(graph, from, x.ComponentID,
+ edge.Label(x.Label),
+ edge.Dir(x.Dir),
+ edge.Color(x.Color),
+ edge.Dashed(x.Dashed),
+ edge.Highlight(x.Highlight))
+
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/examples/aws-cognito-custom-auth-flow.png b/examples/aws-cognito-custom-auth-flow.png
new file mode 100644
index 0000000..1354b1f
Binary files /dev/null and b/examples/aws-cognito-custom-auth-flow.png differ
diff --git a/examples/aws-cognito-custom-auth-flow.yml b/examples/aws-cognito-custom-auth-flow.yml
new file mode 100644
index 0000000..6eff08e
--- /dev/null
+++ b/examples/aws-cognito-custom-auth-flow.yml
@@ -0,0 +1,62 @@
+title: Amazon Cognito Custom Authentication Flow with external database
+backgroundColor: '#ffffff'
+components:
+ -
+ kind: client
+ label: "Web App"
+ provider: SPA
+ -
+ kind: client
+ label: "Mobile App"
+ provider: "Android & iOS"
+ -
+ kind: service
+ label: "Cognito"
+ provider: "AWS Cognito"
+ fillColor: '#991919'
+ fontColor: '#fafafa'
+ -
+ kind: function
+ label: "Define\nAuthChallange"
+ provider: "AWS Lambda"
+ -
+ kind: function
+ label: "Create\nAuthChallange"
+ provider: "AWS Lambda"
+ -
+ kind: function
+ label: "Verify\nAuthChallange"
+ provider: "AWS Lambda"
+ -
+ kind: database
+ label: "Users\nRepository"
+ provider: "AWS RDS"
+connections:
+ -
+ origin:
+ componentId: cl1
+ targets:
+ -
+ componentId: ms1
+ -
+ origin:
+ componentId: cl2
+ targets:
+ -
+ componentId: ms1
+ -
+ origin:
+ componentId: ms1
+ targets:
+ -
+ componentId: fn1
+ -
+ componentId: fn2
+ -
+ componentId: fn3
+ -
+ origin:
+ componentId: fn2
+ targets:
+ -
+ componentId: db1
\ No newline at end of file
diff --git a/examples/backend-for-frontend.png b/examples/backend-for-frontend.png
new file mode 100644
index 0000000..2174d9c
Binary files /dev/null and b/examples/backend-for-frontend.png differ
diff --git a/examples/backend-for-frontend.yml b/examples/backend-for-frontend.yml
new file mode 100644
index 0000000..fc59986
--- /dev/null
+++ b/examples/backend-for-frontend.yml
@@ -0,0 +1,79 @@
+title: Backend For Frontend (BFF)
+backgroundColor: '#ffffff'
+components:
+ -
+ kind: client
+ label: Web App
+ fillColor: '#ee82ee'
+ -
+ kind: client
+ label: Mobile App
+ fillColor: '#708090'
+ -
+ kind: gateway
+ label: "Web BFF\nAPI Gateway"
+ -
+ kind: gateway
+ label: "Mobile BFF\nAPI Gateway"
+ -
+ kind: service
+ label: μService A
+ fillColor: '#b0e0e6'
+ -
+ kind: service
+ label: μService B
+ -
+ kind: service
+ label: μService C
+ fillColor: '#00ff7f'
+ -
+ kind: service
+ label: μService D
+ fillColor: '#00007f'
+ fontColor: '#fafafa'
+connections:
+ -
+ origin:
+ componentId: cl1
+ targets:
+ -
+ componentId: gt1
+ color: '#ee82ee'
+ -
+ origin:
+ componentId: cl2
+ targets:
+ -
+ componentId: gt2
+ -
+ origin:
+ componentId: gt1
+ targets:
+ -
+ componentId: ms1
+ color: '#ee82ee'
+ -
+ componentId: ms2
+ color: '#ee82ee'
+ -
+ componentId: ms3
+ color: '#ee82ee'
+ -
+ componentId: ms4
+ color: '#ee82ee'
+ -
+ origin:
+ componentId: gt2
+ targets:
+ -
+ componentId: ms1
+ highlight: true
+ -
+ componentId: ms2
+ highlight: true
+ -
+ componentId: ms3
+ highlight: true
+ -
+ componentId: ms4
+ highlight: true
\ No newline at end of file
diff --git a/examples/br.jpg b/examples/br.jpg
new file mode 100644
index 0000000..2d7c08a
Binary files /dev/null and b/examples/br.jpg differ
diff --git a/examples/broker.png b/examples/broker.png
new file mode 100644
index 0000000..6adb5c7
Binary files /dev/null and b/examples/broker.png differ
diff --git a/examples/broker.yml b/examples/broker.yml
new file mode 100644
index 0000000..2ceb258
--- /dev/null
+++ b/examples/broker.yml
@@ -0,0 +1,5 @@
+---
+components:
+ -
+ kind: broker
+ label: "Message Broker"
\ No newline at end of file
diff --git a/examples/broker_impl.yml b/examples/broker_impl.yml
new file mode 100644
index 0000000..bbfd5ae
--- /dev/null
+++ b/examples/broker_impl.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: broker
+ label: "Message Broker"
+ provider: AWS SNS
\ No newline at end of file
diff --git a/examples/cl.jpg b/examples/cl.jpg
new file mode 100644
index 0000000..9859aeb
Binary files /dev/null and b/examples/cl.jpg differ
diff --git a/examples/cl.png b/examples/cl.png
new file mode 100644
index 0000000..dfd2e6e
Binary files /dev/null and b/examples/cl.png differ
diff --git a/examples/client.png b/examples/client.png
new file mode 100644
index 0000000..875542f
Binary files /dev/null and b/examples/client.png differ
diff --git a/examples/client.yml b/examples/client.yml
new file mode 100644
index 0000000..a656f77
--- /dev/null
+++ b/examples/client.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: client
+ label: "Web App"
+ fillColor: '#ee82ee'
diff --git a/examples/client_impl.yml b/examples/client_impl.yml
new file mode 100644
index 0000000..5989bfb
--- /dev/null
+++ b/examples/client_impl.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: client
+ label: "Web App"
+ provider: SPA
diff --git a/examples/database.png b/examples/database.png
new file mode 100644
index 0000000..3e92d28
Binary files /dev/null and b/examples/database.png differ
diff --git a/examples/database.yml b/examples/database.yml
new file mode 100644
index 0000000..55060fb
--- /dev/null
+++ b/examples/database.yml
@@ -0,0 +1,5 @@
+---
+components:
+ -
+ kind: database
+ label: "Users\nRepository"
diff --git a/examples/database_impl.yml b/examples/database_impl.yml
new file mode 100644
index 0000000..6d464cc
--- /dev/null
+++ b/examples/database_impl.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: database
+ label: "Users\nRepository"
+ provider: AWS RDS
diff --git a/examples/db.jpg b/examples/db.jpg
new file mode 100644
index 0000000..dedfdea
Binary files /dev/null and b/examples/db.jpg differ
diff --git a/examples/fn.jpg b/examples/fn.jpg
new file mode 100644
index 0000000..d5b33d4
Binary files /dev/null and b/examples/fn.jpg differ
diff --git a/examples/function.png b/examples/function.png
new file mode 100644
index 0000000..12c3847
Binary files /dev/null and b/examples/function.png differ
diff --git a/examples/function.yml b/examples/function.yml
new file mode 100644
index 0000000..06dbe4c
--- /dev/null
+++ b/examples/function.yml
@@ -0,0 +1,5 @@
+---
+components:
+ -
+ kind: function
+ label: "Create\nAuth Challange"
diff --git a/examples/function_impl.yml b/examples/function_impl.yml
new file mode 100644
index 0000000..58f7ba1
--- /dev/null
+++ b/examples/function_impl.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: function
+ label: "Create\nAuth Challange"
+ provider: AWS Lambda
diff --git a/examples/gateway.png b/examples/gateway.png
new file mode 100644
index 0000000..24b5695
Binary files /dev/null and b/examples/gateway.png differ
diff --git a/examples/gateway.yml b/examples/gateway.yml
new file mode 100644
index 0000000..c669810
--- /dev/null
+++ b/examples/gateway.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: gateway
+ label: "API Gateway"
+ fontColor: '#fafafaff'
\ No newline at end of file
diff --git a/examples/gateway_impl.yml b/examples/gateway_impl.yml
new file mode 100644
index 0000000..9f6ade4
--- /dev/null
+++ b/examples/gateway_impl.yml
@@ -0,0 +1,7 @@
+---
+components:
+ -
+ kind: gateway
+ label: "API Gateway"
+ fontColor: '#fafafaff'
+ provider: "AWS API\nGateway"
\ No newline at end of file
diff --git a/examples/gt.jpg b/examples/gt.jpg
new file mode 100644
index 0000000..27631b5
Binary files /dev/null and b/examples/gt.jpg differ
diff --git a/examples/message-bus-pattern.png b/examples/message-bus-pattern.png
new file mode 100644
index 0000000..d19fabf
Binary files /dev/null and b/examples/message-bus-pattern.png differ
diff --git a/examples/message-bus-pattern.yml b/examples/message-bus-pattern.yml
new file mode 100644
index 0000000..cd64f1a
--- /dev/null
+++ b/examples/message-bus-pattern.yml
@@ -0,0 +1,58 @@
+title: message bus pattern
+backgroundColor: '#ffffff'
+components:
+ -
+ kind: service
+ label: Producer
+ provider: AWS EC2
+ -
+ kind: broker
+ label: "Notification\nService"
+ provider: AWS SNS
+ -
+ kind: queue
+ label: "event queue @ topic 1"
+ provider: AWS SQS
+ -
+ kind: queue
+ label: "event queue @ topic 2"
+ provider: AWS SQS
+ -
+ kind: service
+ label: "Consumer\n@ topic 1"
+ provider: AWS EC2
+ -
+ kind: service
+ label: "Consumer\n@ topic 2"
+ provider: AWS EC2
+connections:
+ -
+ origin:
+ componentId: ms1
+ targets:
+ -
+ componentId: br1
+ -
+ origin:
+ componentId: br1
+ targets:
+ -
+ componentId: qs1
+ dashed: true
+ -
+ componentId: qs2
+ dashed: true
+ -
+ origin:
+ componentId: qs1
+ targets:
+ -
+ componentId: ms2
+ dir: back
+ -
+ origin:
+ componentId: qs2
+ targets:
+ -
+ componentId: ms3
+ dir: back
diff --git a/examples/ms.jpg b/examples/ms.jpg
new file mode 100644
index 0000000..d48fe24
Binary files /dev/null and b/examples/ms.jpg differ
diff --git a/examples/qs.jpg b/examples/qs.jpg
new file mode 100644
index 0000000..ffaf71b
Binary files /dev/null and b/examples/qs.jpg differ
diff --git a/examples/queue.png b/examples/queue.png
new file mode 100644
index 0000000..313c627
Binary files /dev/null and b/examples/queue.png differ
diff --git a/examples/queue.yml b/examples/queue.yml
new file mode 100644
index 0000000..1b3baab
--- /dev/null
+++ b/examples/queue.yml
@@ -0,0 +1,5 @@
+---
+components:
+ -
+ kind: queue
+ label: "event queue"
\ No newline at end of file
diff --git a/examples/queue_impl.yml b/examples/queue_impl.yml
new file mode 100644
index 0000000..a1336b7
--- /dev/null
+++ b/examples/queue_impl.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: queue
+ label: "event queue"
+ provider: "AWS SQS"
\ No newline at end of file
diff --git a/examples/service.png b/examples/service.png
new file mode 100644
index 0000000..56fe2a7
Binary files /dev/null and b/examples/service.png differ
diff --git a/examples/service.yml b/examples/service.yml
new file mode 100644
index 0000000..705a759
--- /dev/null
+++ b/examples/service.yml
@@ -0,0 +1,5 @@
+---
+components:
+ -
+ kind: service
+ label: "μService"
diff --git a/examples/service_impl.yml b/examples/service_impl.yml
new file mode 100644
index 0000000..c7f6080
--- /dev/null
+++ b/examples/service_impl.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: service
+ label: "μService"
+ provider: AWS EC2
diff --git a/examples/st.jpg b/examples/st.jpg
new file mode 100644
index 0000000..3e103a1
Binary files /dev/null and b/examples/st.jpg differ
diff --git a/examples/storage.png b/examples/storage.png
new file mode 100644
index 0000000..c945571
Binary files /dev/null and b/examples/storage.png differ
diff --git a/examples/storage.yml b/examples/storage.yml
new file mode 100644
index 0000000..8a0870e
--- /dev/null
+++ b/examples/storage.yml
@@ -0,0 +1,5 @@
+---
+components:
+ -
+ kind: storage
+ label: "*.jpg\n*.png"
\ No newline at end of file
diff --git a/examples/storage_impl.yml b/examples/storage_impl.yml
new file mode 100644
index 0000000..dc539bc
--- /dev/null
+++ b/examples/storage_impl.yml
@@ -0,0 +1,6 @@
+---
+components:
+ -
+ kind: storage
+ label: "*.jpg\n*.png"
+ provider: "AWS S3"
\ No newline at end of file
diff --git a/function.go b/function.go
new file mode 100644
index 0000000..5642f20
--- /dev/null
+++ b/function.go
@@ -0,0 +1,38 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type function struct {
+ seq int16
+}
+
+func (rcv *function) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("fn%d", rcv.seq)
+}
+
+func (rcv *function) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(comp.Label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor(comp.FillColor, "#abd9e9ff"),
+ node.Shape("signature"),
+ )
+ el.Attr("fontsize", "6")
+ el.Attr("height", "0.5")
+}
diff --git a/gateway.go b/gateway.go
new file mode 100644
index 0000000..c61ce98
--- /dev/null
+++ b/gateway.go
@@ -0,0 +1,43 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type gateway struct {
+ seq int16
+}
+
+func (rcv *gateway) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("gt%d", rcv.seq)
+}
+
+func (rcv *gateway) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ label := comp.Label
+ if strings.TrimSpace(comp.Label) == "" {
+ label = "API Gateway"
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor(comp.FillColor, "#ff7f00ff"),
+ node.FontSize(7),
+ node.Shape("point"),
+ )
+ el.Attr("width", "0.3")
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..65e2863
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,8 @@
+module github.com/lucasepe/draft
+
+go 1.14
+
+require (
+ github.com/emicklei/dot v0.11.0
+ gopkg.in/yaml.v2 v2.3.0
+)
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
new file mode 100644
index 0000000..170565b
--- /dev/null
+++ b/pkg/cluster/cluster.go
@@ -0,0 +1,65 @@
+package cluster
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+)
+
+type Attribute func(*dot.Graph)
+
+func Label(label string) Attribute {
+ return func(el *dot.Graph) {
+ if strings.TrimSpace(label) != "" {
+ el.Attr("label", label)
+ el.Attr("pencolor", "#f5deb3")
+ el.Attr("style", "dashed")
+ }
+ }
+}
+
+func PenColor(color string) Attribute {
+ return func(el *dot.Graph) {
+ if strings.TrimSpace(color) != "" {
+ el.Attr("pencolor", color)
+ }
+ }
+}
+
+func FontColor(color string) Attribute {
+ return func(el *dot.Graph) {
+ if strings.TrimSpace(color) != "" {
+ el.Attr("fontcolor", color)
+ } else {
+ el.Attr("fontcolor", "#000000ff")
+ }
+ }
+}
+
+func FontName(name string) Attribute {
+ return func(el *dot.Graph) {
+ el.Attr("fontname", name)
+ }
+}
+
+func FontSize(size float32) Attribute {
+ return func(el *dot.Graph) {
+ fs := fmt.Sprintf("%.2f", size)
+ el.Attr("fontsize", fs)
+ }
+}
+
+func New(parent *dot.Graph, id string, attrs ...Attribute) *dot.Graph {
+ cluster := parent.Subgraph(id, dot.ClusterOption{})
+
+ // default attributes
+ FontName("Fira Mono Bold")(cluster)
+ FontSize(9)(cluster)
+ PenColor("transparent")(cluster)
+
+ for _, opt := range attrs {
+ opt(cluster)
+ }
+ return cluster
+}
diff --git a/pkg/edge/edge.go b/pkg/edge/edge.go
new file mode 100644
index 0000000..afce977
--- /dev/null
+++ b/pkg/edge/edge.go
@@ -0,0 +1,105 @@
+package edge
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+)
+
+type Attribute func(*dot.Edge)
+
+func Label(label string) Attribute {
+ return func(el *dot.Edge) {
+ el.Attr("label", label)
+ }
+}
+
+func FontName(name string) Attribute {
+ return func(el *dot.Edge) {
+ el.Attr("fontname", name)
+ }
+}
+
+func FontSize(size float32) Attribute {
+ return func(el *dot.Edge) {
+ fs := fmt.Sprintf("%.2f", size)
+ el.Attr("fontsize", fs)
+ }
+}
+
+func Dir(dir string) Attribute {
+ return func(el *dot.Edge) {
+ if strings.TrimSpace(dir) != "" {
+ el.Attr("dir", dir)
+ }
+ }
+}
+
+func Dashed(dashed bool) Attribute {
+ return func(el *dot.Edge) {
+ if dashed {
+ el.Attr("style", "dashed")
+ }
+ }
+}
+
+func Color(color string) Attribute {
+ return func(el *dot.Edge) {
+ if strings.TrimSpace(color) != "" {
+ el.Attr("color", color)
+ } else {
+ el.Attr("color", "#708090ff")
+ }
+ }
+}
+
+func PenWidth(size float32) Attribute {
+ return func(el *dot.Edge) {
+ pw := fmt.Sprintf("%.2f", size)
+ el.Attr("penwidth", pw)
+ }
+}
+
+func ArrowSize(size float32) Attribute {
+ return func(el *dot.Edge) {
+ pw := fmt.Sprintf("%.2f", size)
+ el.Attr("arrowsize", pw)
+ }
+}
+
+func Highlight(ok bool) Attribute {
+ return func(el *dot.Edge) {
+ if ok {
+ el.Attr("penwidth", "1.2")
+ el.Attr("arrowsize", "0.9")
+ } else {
+ el.Attr("penwidth", "0.6")
+ el.Attr("arrowsize", "0.6")
+ }
+ }
+}
+
+func New(g *dot.Graph, fromNodeID, toNodeID string, attrs ...Attribute) error {
+ n1, ok := g.FindNodeById(fromNodeID)
+ if !ok {
+ return fmt.Errorf("node with id=%s not found", fromNodeID)
+ }
+
+ n2, ok := g.FindNodeById(toNodeID)
+ if !ok {
+ return fmt.Errorf("node with id=%s not found", toNodeID)
+ }
+
+ el := g.Edge(n1, n2)
+
+ FontName("Fira Mono")(&el)
+ FontSize(8)(&el)
+ Highlight(false)(&el)
+
+ for _, opt := range attrs {
+ opt(&el)
+ }
+
+ return nil
+}
diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go
new file mode 100644
index 0000000..81fdcd6
--- /dev/null
+++ b/pkg/graph/graph.go
@@ -0,0 +1,75 @@
+package graph
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+)
+
+type Attribute func(*dot.Graph)
+
+func Label(label string) Attribute {
+ return func(el *dot.Graph) {
+ if strings.TrimSpace(label) != "" {
+ el.Attr("label", label)
+ }
+ }
+}
+
+func FontName(name string) Attribute {
+ return func(el *dot.Graph) {
+ el.Attr("fontname", name)
+ }
+}
+
+func FontSize(size float32) Attribute {
+ return func(el *dot.Graph) {
+ fs := fmt.Sprintf("%.2f", size)
+ el.Attr("fontsize", fs)
+ }
+}
+
+func LeftToRight() Attribute {
+ return func(el *dot.Graph) {
+ el.Attr("rankdir", "LR")
+ }
+}
+
+func TopToBottom() Attribute {
+ return func(el *dot.Graph) {
+ el.Attr("rankdir", "TB")
+ }
+}
+
+func RankSep(size float32) Attribute {
+ return func(el *dot.Graph) {
+ fs := fmt.Sprintf("%.2f", size)
+ el.Attr("ranksep", fs)
+ }
+}
+
+func BackgroundColor(color string) Attribute {
+ return func(el *dot.Graph) {
+ if strings.TrimSpace(color) != "" {
+ el.Attr("bgcolor", color)
+ } else {
+ el.Attr("bgcolor", "transparent")
+ }
+ }
+}
+
+func New(attrs ...Attribute) *dot.Graph {
+ el := dot.NewGraph(dot.Directed)
+
+ FontName("Fira Mono Bold")(el)
+ FontSize(13)(el)
+ LeftToRight()(el)
+ RankSep(1.1)(el)
+
+ for _, opt := range attrs {
+ opt(el)
+ }
+
+ return el
+}
diff --git a/pkg/node/node.go b/pkg/node/node.go
new file mode 100644
index 0000000..892d4d0
--- /dev/null
+++ b/pkg/node/node.go
@@ -0,0 +1,79 @@
+package node
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+)
+
+type Attribute func(*dot.Node)
+
+func Label(label string) Attribute {
+ return func(el *dot.Node) {
+ el.Attr("label", label)
+ }
+}
+
+func Shape(shape string) Attribute {
+ return func(el *dot.Node) {
+ el.Attr("shape", shape)
+ }
+}
+
+func Rounded(rounded bool) Attribute {
+ return func(el *dot.Node) {
+ if rounded {
+ el.Attr("style", "rounded,filled")
+ } else {
+ el.Attr("style", "filled")
+ }
+ }
+}
+
+func FillColor(color, fallback string) Attribute {
+ return func(el *dot.Node) {
+ if strings.TrimSpace(color) != "" {
+ el.Attr("fillcolor", color)
+ } else {
+ el.Attr("fillcolor", fallback)
+ }
+ }
+}
+
+func FontColor(color string) Attribute {
+ return func(el *dot.Node) {
+ if strings.TrimSpace(color) != "" {
+ el.Attr("fontcolor", color)
+ } else {
+ el.Attr("fontcolor", "#000000ff")
+ }
+ }
+}
+
+func FontName(name string) Attribute {
+ return func(el *dot.Node) {
+ el.Attr("fontname", name)
+ }
+}
+
+func FontSize(size float32) Attribute {
+ return func(el *dot.Node) {
+ fs := fmt.Sprintf("%.2f", size)
+ el.Attr("fontsize", fs)
+ }
+}
+
+func New(cluster *dot.Graph, id string, attrs ...Attribute) *dot.Node {
+ el := cluster.Node(id)
+
+ // default attributes
+ FontName("Fira Mono")(&el)
+ FontSize(9)(&el)
+
+ for _, opt := range attrs {
+ opt(&el)
+ }
+
+ return &el
+}
diff --git a/queue.go b/queue.go
new file mode 100644
index 0000000..ebd5f58
--- /dev/null
+++ b/queue.go
@@ -0,0 +1,58 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type queue struct {
+ seq int16
+}
+
+func (rcv *queue) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("qs%d", rcv.seq)
+}
+
+func (rcv *queue) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(comp.Label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor("", "transparent"),
+ // ^^^ hack to set a transparent background
+ // color since we will use the HTML table.
+ node.Shape("plaintext"),
+ )
+
+ caption := strings.TrimSpace(comp.Label)
+ if len(caption) == 0 {
+ caption = " "
+ }
+
+ fillColor := comp.FillColor
+ if strings.TrimSpace(comp.FillColor) == "" {
+ fillColor = "#bdb76bff"
+ }
+
+ label := fmt.Sprintf(`
+ |
+ msg N |
+ ... |
+ msg 1 |
+ %s |
+
`, fillColor, fillColor, fillColor, caption)
+
+ el.Attr("label", dot.HTML(label))
+}
diff --git a/service.go b/service.go
new file mode 100644
index 0000000..ab53eaf
--- /dev/null
+++ b/service.go
@@ -0,0 +1,37 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type service struct {
+ seq int16
+}
+
+func (rcv *service) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("ms%d", rcv.seq)
+}
+
+func (rcv *service) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(comp.Label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor(comp.FillColor, "#f5f5dcff"),
+ node.Shape("box"),
+ )
+ el.Attr("height", "0.5")
+}
diff --git a/storage.go b/storage.go
new file mode 100644
index 0000000..09135b1
--- /dev/null
+++ b/storage.go
@@ -0,0 +1,38 @@
+package draft
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/emicklei/dot"
+ "github.com/lucasepe/draft/pkg/cluster"
+ "github.com/lucasepe/draft/pkg/node"
+)
+
+type storage struct {
+ seq int16
+}
+
+func (rcv *storage) nextID() string {
+ rcv.seq++
+ return fmt.Sprintf("st%d", rcv.seq)
+}
+
+func (rcv *storage) sketch(graph *dot.Graph, comp Component) {
+ id := comp.ID
+ if strings.TrimSpace(comp.ID) == "" {
+ id = rcv.nextID()
+ }
+
+ cl := cluster.New(graph, id, cluster.Label(comp.Provider))
+
+ el := node.New(cl, id,
+ node.Label(comp.Label),
+ node.Rounded(comp.Rounded),
+ node.FontColor(comp.FontColor),
+ node.FillColor(comp.FillColor, "#f0e77fff"),
+ node.FontSize(8),
+ node.Shape("folder"),
+ )
+ el.Attr("height", "0.4")
+}