README
¶
Structurizr for Go
Overview
This GitHub repository is a non-official client library and tool for the Structurizr cloud service and on-premises installation, both of which are web-based publishing platforms for software architecture models based upon the C4 model.
The repository defines a Go DSL that makes it convenient to describe the software architecture model so that it can be uploaded to the Structurizr service.
This library also provides a Goa plugin so that the design of APIs and microservices written in Goa can be augmented with a description of the corresponding software architecture.
Example
Here is a complete and correct DSL for an architecture model:
package model
import . "goa.design/structurizr/dsl"
var _ = Workspace("Getting Started", "This is a model of my software system.", func() {
var System = SoftwareSystem("Software System", "My software system.", func() {
Tag("system")
})
Person("User", "A user of my software system.", func() {
Uses(System, "Uses")
Tag("person")
})
Views(func() {
SystemContextView(System, "SystemContext", "An example of a System Context diagram.", func() {
AddAll()
AutoLayout(RankLeftRight)
})
Styles(func() {
ElementStyle("system", func() {
Background("#1168bd")
Color("#ffffff")
})
ElementStyle("person", func() {
Shape(ShapePerson)
Background("#08427b")
Color("#ffffff")
})
})
})
})
This code creates a model containing elements and relationships, creates a
single view and adds some styling.
Other examples can be found in the repo examples directory.
Library
The eval package makes it convenient to run the DSL above. The service package contains a client library for the Structurizr service APIs.
Here is a complete example that takes advantage of both to upload the workspace described in a DSL to the Structurizr service:
package main
import (
"fmt"
"os"
. "goa.design/structurizr/dsl"
"goa.design/structurizr/eval"
"goa.design/structurizr/service"
)
// DSL that describes software architecture model.
var _ = Workspace("Getting Started", "This is a model of my software system.", func() {
var System = SoftwareSystem("Software System", "My software system.", func() {
Tag("system")
})
Person("User", "A user of my software system.", func() {
Uses(System, "Uses")
Tag("person")
})
Views(func() {
SystemContextView(System, "SystemContext", "An example of a System Context diagram.", func() {
AddAll()
AutoLayout(RankLeftRight)
})
Styles(func() {
ElementStyle("system", func() {
Background("#1168bd")
Color("#ffffff")
})
ElementStyle("person", func() {
Shape(ShapePerson)
Background("#08427b")
Color("#ffffff")
})
})
})
})
// Executes the DSL and uploads the corresponding workspace to Structurizr.
func main() {
// Run the model DSL
w, err := eval.RunDSL()
if err != nil {
fmt.Fprintf(os.Stderr, "invalid model: %s", err.Error())
os.Exit(1)
}
// Upload the model to the Structurizr service.
// The API key and secret must be set in the STRUCTURIZR_KEY and
// STRUCTURIZR_SECRET environment variables respectively. The
// workspace ID must be set in STRUCTURIZR_WORKSPACE_ID.
var (
key = os.Getenv("STRUCTURIZR_KEY")
secret = os.Getenv("STRUCTURIZR_SECRET")
wid = os.Getenv("STRUCTURIZR_WORKSPACE_ID")
)
c := service.NewClient(key, secret)
if err := c.Put(wid, w); err != nil {
fmt.Fprintf(os.Stderr, "failed to store workspace: %s", err.Error())
os.Exit(1)
}
}
Tool
Alternatively, the stz
tool included in this repo can be used to generate a
file containing the JSON representation of the structurizr API
Workspace object described via DSL.
The tool can can also retrieve or upload such files from and to the
Structurizr service. Finally the tool can also lock or unlock a workspace in
the service.
Upload DSL defined in package goa.design/structurizr/examples/basic
:
stz gen goa.design/structurizr/examples/basic && stz put -id ID -key KEY -secret SECRET
Where ID
is the Structurizr service workspace ID, KEY
the
Structurizr service API key and SECRET
the corresponding secret.
Retrieve the JSON representation of a workspace from the service:
stz get -id ID -key KEY -secret SECRET -out model.json
Upload an existing file to the Structurizr service:
stz put model.json -id ID -key KEY -secret SECRET
Tool Setup
Assuming a working Go setup, run the following command in the root of the repo:
go install cmd/stz
This will create a stz
executable under $GOPATH/bin
which should be in
your PATH
environment variable.
Goa Plugin
This package can also be used as a Goa plugin by including the DSL package in the Goa design:
package design
import . "goa.design/goa/v3/dsl"
import "goa.design/structurizr/dsl"
// ... DSL describing API, services and architecture model
Running goa gen
creates a model.json
file in the gen
folder. This
file follows the
structurizr JSON schema and can be
uploaded to the Structurizr service for example using the stz
tool included
in this repo.
DSL Syntax
Rules
The following rules apply to all elements and views declared in a model:
- Software and people names must be unique.
- Container names must be unique within the context of a software system.
- Component names must be unique within the context of a container.
- Deployment node names must be unique with their parent context.
- Infrastructure node names must be unique with their parent context.
- All relationships from a given source element to a given destination element must have a unique description.
- View keys must be unique.
Note that uniqueness of names is enforced by combining the evaluated definitions for a given element. For example if a model contained:
var Person1 = Person("User", "A user", func() {
Uses("System 1", "Uses")
})
var Person2 = Person("User", "The same user again", func() {
Uses("System 2", "Uses")
})
Then the final model would only define a single person named "User"
with
the description "The same user again"
and both relationships. This makes it
possible to import shared models and "edit" existing elements, for example to
add new relationships.
References
The functions Uses
, Delivers
, InteractsWith
, Add
and Link
accept
references to other elements as argument. The reference can be done either
through a variable (which holds the element being referred to) or the name of
the element. Note that names do not necessarily have to be globally unique
(see rules above) so it may sometimes be necessary to use a variable to
disambiguate. Also container instances do not have names per se however the
RefName
function makes it possible to define a name that can be used to
refer to the container instance in deployment views (when using Add
or
Link
).
Syntax
The code snippet below describes the entire syntax of the DSL. The complete
reference can be found in the dsl
package documentation
// Workspace defines the workspace containing the models and views. Workspace
// must appear exactly once in a given design. A name must be provided if a
// description is.
var _ = Workspace("[name]", "[description]", func() {
// Version number.
Version("<version>")
// Enterprise defines a named "enterprise" (e.g. an organisation). On System
// Landscape and System Context diagrams, an enterprise is represented as a
// dashed box. Only a single enterprise can be defined within a model.
Enterprise("<name>")
// Person defines a person (user, actor, role or persona).
var Person = Person("<name>", "[description]", func() {
Tag("<name>", "[name]") // as many tags as needed
// URL where more information about this system can be found.
URL("<url>")
// External indicates the person is external to the enterprise.
External()
// Prop defines an arbitrary set of associated key-value pairs.
Prop("<name>", "<value">)
// Adds a uni-directional relationship between this person and the given element.
Uses(Element, "<description>", "[technology]", Synchronous /* or Asynchronous */, func() {
Tag("<name>", "[name]") // as many tags as needed
})
// Adds an interaction between this person and another.
InteractsWith(Person, "<description>", "[technology]", Synchronous /* or Asynchronous */, func() {
Tag("<name>", "[name]") // as many tags as needed
})
})
// SoftwareSystem defines a software system.
var SoftwareSystem = SoftwareSystem("<name>", "[description]", func() {
Tag("<name>", "[name]") // as many tags as needed
// URL where more information about this software system can be
// found.
URL("<url>")
// External indicates the software system is external to the enterprise.
External()
// Prop defines an arbitrary set of associated key-value pairs.
Prop("<name>", "<value">)
// Adds a uni-directional relationship between this software system and the given element.
Uses(Element, "<description>", "[technology]", Synchronous /* or Asynchronous */, func() {
Tag("<name>", "[name]") // as many tags as needed
})
// Adds an interaction between this software system and a person.
Delivers(Person, "<description>", "[technology]", Synchronous /* or Asynchronous */, func() {
Tag("<name>", "[name]") // as many tags as needed
})
// Container defines a container within a software system.
var Container = Container("<name>", "[description]", "[technology]", func() {
Tag("<name>", "[name]") // as many tags as neede
// URL where more information about this container can be found.
URL("<url>")
// Prop defines an arbitrary set of associated key-value pairs.
Prop("<name>", "<value">)
// Adds a uni-directional relationship between this container and the given element.
Uses(Element, "<description>", "[technology]", Synchronous /* or Asynchronous */, func () {
Tag("<name>", "[name]") // as many tags as needed
})
// Adds an interaction between this container and a person.
Delivers(Person, "<description>", "[technology]", Synchronous /* or Asynchronous */, func() {
Tag("<name>", "[name]") // as many tags as needed
})
})
// Container may also refer to a Goa service in which case the name
// and description are taken from the given service definition and
// the technology is set to "Go and Goa v3".
var Container = Container(GoaService, func() {
// ... see above
// Component defines a component within a container.
var Component = Component("<name>", "[description]", "[technology]", func() {
Tag("<name>", "[name]") // as many tags as need
// URL where more information about this container can be found.
URL("<url>")
// Prop defines an arbitrary set of associated key-value pairs.
Prop("<name>", "<value">)
// Adds a uni-directional relationship between this component and the given element.
Uses(Element, "<description>", "[technology]", Synchronous /* or Asynchronous */, func() {
Tag("<name>", "[name]") // as many tags as needed
})
// Adds an interaction between this component and a person.
Delivers(Person, "<description>", "[technology]", Synchronous /* or Asynchronous */, func() {
Tag("<name>", "[name]") // as many tags as needed
})
})
})
})
// DeploymentEnvironment provides a way to define a deployment
// environment (e.g. development, staging, production, etc).
DeploymentEnvironment("<name>", func() {
// DeploymentNode defines a deployment node. Deployment nodes can be
// nested, so a deployment node can contain other deployment nodes.
// A deployment node can also contain InfrastructureNode and
// ContainerInstance elements.
var DeploymentNode = DeploymentNode("<name>", "[description]", "[technology]", func() {
Tag("<name>", "[name]") // as many tags as needed
// Instances sets the number of instances, defaults to 1.
Instances(2)
// URL where more information about this deployment node can be
// found.
URL("<url>")
// Prop defines an arbitrary set of associated key-value pairs.
Prop("<name>", "<value">)
// InfrastructureNode defines an infrastructure node, typically
// something like a load balancer, firewall, DNS service, etc.
var InfrastructureNode = InfrastructureNode("<name>", "[description]", "[technology]", func() {
Tag("<name>", "[name]") // as many tags as needed
// URL where more information about this infrastructure node can be
// found.
URL("<url>")
// Prop defines an arbitrary set of associated key-value pairs.
Prop("<name>", "<value">)
})
// ContainerInstance defines an instance of the specified
// container that is deployed on the parent deployment node.
var ContainerInstance = ContainerInstance(Container, func() {
Tag("<name>", "[name]") // as many tags as needed
// Sets a name that can be used in deployment views.
RefName("<name>")
// Sets instance number or index.
InstanceID(1)
// Prop defines an arbitrary set of associated key-value pairs.
Prop("<name>", "<value">)
// HealthCheck defines a HTTP-based health check for this
// container instance.
HealthCheck("<name>", func() {
// URL is the health check URL/endpoint.
URL("<url>")
// Interval is the polling interval, in seconds.
Interval(42)
// Timeout after which a health check is deemed as failed,
// in milliseconds.
Timeout(42)
// Header defines a header that should be sent with the
// request.
Header("<name>", "<value>")
})
})
// DeploymentNode within a deployment node defines child nodes.
var ChildNode = DeploymentNode("<name>", "[description]", "[technology]", func() {
// ... see above
})
})
})
// Views is optional and defines one or more views.
Views(func() {
// SystemLandscapeView defines a System Landscape view.
SystemLandscapeView("[key]", "[description]", func() {
// Title of this view.
Title("<title>")
// AddDefault adds default elements that are relevant for the
// specific view:
//
// - System landscape view: adds all software systems and people
// - System context view: adds softare system and other related
// software systems and people.
// - Container view: adds all containers in software system as well
// as related software systems and people.
// - Component view: adds all components in container as well as
// related containers, software systems and people.
AddDefault()
// Add given person or element to view. If person or element was
// already added implictely (e.g. via AddAll()) then overrides how
// the person or element is rendered.
Add(PersonOrElement, func() {
// Set explicit coordinates for where to render person or
// element.
Coord(X, Y)
// Do not render relationships when rendering person or element.
NoRelationship()
})
// Add given relationship to view. If relationship was already added
// implictely (e.g. via AddAll()) then overrides how the
// relationship is rendered.
Link(Source, Destination, func() {
// Vertices lists the x and y coordinate of the vertices used to
// render the relationship. The number of arguments must be even.
Vertices(10, 20, 10, 40)
// Routing algorithm used when rendering relationship, one of
// RoutingDirect, RoutingCurved or RoutingOrthogonal.
Routing(RoutingOrthogonal)
// Position of annotation along line; 0 (start) to 100 (end).
Position(50)
})
// Add all elements and people in scope.
AddAll()
// Add default set of elements depending on view type.
AddDefault()
// Add all elements that are directly connected to given person
// or element.
AddNeighbors(PersonOrElement)
// Remove given element or person from view.
Remove(ElementOrPerson)
// Remove given relationship from view.
Remove(Source, Destination)
// Remove elements and relationships with given tag.
Remove("<tag>")
// Remove all elements and people that cannot be reached by
// traversing the graph of relationships starting with given element
// or person.
RemoveUnreachable(ElementOrPerson)
// Remove all elements that have no relationships to other elements.
RemoveUnrelated()
// AutoLayout enables automatic layout mode for the diagram. The
// first argument indicates the rank direction, it must be one of
// RankTopBottom, RankBottomTop, RankLeftRight or RankRightLeft.
AutoLayout(RankTopBottom, func() {
// Separation between ranks in pixels, defaults to 300.
RankSeparation(300)
// Separation between nodes in the same rank in pixels, defaults to 600.
NodeSeparation(600)
// Separation between edges in pixels, defaults to 200.
EdgeSeparation(200)
// Create vertices during automatic layout, false by default.
RenderVertices()
})
// Animation defines an animation step consisting of the
// specified elements.
Animation(Element, Element/*, ...*/)
// PaperSize defines the paper size that should be used to render
// the view. The possible values for the argument follow the
// patterns SizeA[0-6][Portrait|Landscape], SizeLetter[Portrait|Landscape]
// or SizeLegal[Portrait_Landscape]. Alternatively the argument may be
// one of SizeSlide4X3, SizeSlide16X9 or SizeSlide16X10.
PaperSize(SizeSlide4X3)
// Make enterprise boundary visible to differentiate internal
// elements from external elements on the resulting diagram.
EnterpriseBoundaryVisible()
})
SystemContextView(SoftwareSystem, "[key]", "[description]", func() {
// ... same usage as SystemLandscapeView.
})
ContainerView(SoftwareSystem, "[key]", "[description]", func() {
// ... same usage as SystemLandscapeView without EnterpriseBoundaryVisible.
// All all containers in software system to view.
AddContainers()
// Make software system boundaries visible for "external" containers
// (those outside the software system in scope).
SystemBoundariesVisible()
})
ComponentView(Container, "[key]", "[description]", func() {
// ... same usage as SystemLandscapeView without EnterpriseBoundaryVisible.
// All all containers in software system to view.
AddContainers()
// All all components in container to view.
AddComponents()
// Make container boundaries visible for "external" components
// (those outside the container in scope).
ContainerBoundariesVisible()
})
// FilteredView defines a Filtered view on top of the specified view.
// The given view must be a System Landscape, System Context, Container,
// or Component view on which this filtered view should be based.
FilteredView(View, func() {
// Set of tags to include or exclude (if Exclude() is used)
// elements/relationships when rendering this filtered view.
FilterTag("<tag>", "[tag]") // as many as needed
// Exclude elements and relationships with the given tags instead of
// including.
Exclude()
})
// DynamicView defines a Dynamic view for the specified scope. The
// first argument defines the scope of the view, and therefore what can
// be added to the view, as follows:
//
// * Global scope: People and software systems.
// * Software system scope: People, other software systems, and
// containers belonging to the software system.
// * Container scope: People, other software systems, other
// containers, and components belonging to the container.
DynamicView(Global, "[key]", "[description]", func() {
// Title of this view.
Title("<title>")
// AutoLayout enables automatic layout mode for the diagram. The
// first argument indicates the rank direction, it must be one of
// RankTopBottom, RankBottomTop, RankLeftRight or RankRightLeft.
AutoLayout(RankTopBottom, func() {
// Separation between ranks in pixels
RankSeparation(200)
// Separation between nodes in the same rank in pixels
NodeSeparation(200)
// Separation between edges in pixels
EdgeSeparation(10)
// Create vertices during automatic layout.
Vertices()
})
// PaperSize defines the paper size that should be used to render
// the view. The possible values for the argument follow the
// patterns SizeA[0-6][Portrait|Landscape], SizeLetter[Portrait|Landscape]
// or SizeLegal[Portrait_Landscape]. Alternatively the argument may be
// one of SizeSlide4X3, SizeSlide16X9 or SizeSlide16X10.
PaperSize(SizeSlide4X3)
// Set of relationships that make up dynamic diagram.
Link(Source, Destination, func() {
// Vertices lists the x and y coordinate of the vertices used to
// render the relationship. The number of arguments must be even.
Vertices(10, 20, 10, 40)
// Routing algorithm used when rendering relationship, one of
// RoutingDirect, RoutingCurved or RoutingOrthogonal.
Routing(RoutingOrthogonal)
// Position of annotation along line; 0 (start) to 100 (end).
Position(50)
// Description used in dynamic views.
Description("<description>")
// Order of relationship in dynamic views, e.g. 1.0, 1.1, 2.0
Order("<order>")
})
})
// DynamicView on software system or container uses the corresponding
// identifier as first argument.
DynamicView(SoftwareSystemOrContainer, "[key]", "[description]", func() {
// see usage above
})
// DeploymentView defines a Deployment view for the specified scope and
// deployment environment. The first argument defines the scope of the
// view, and the second property defines the deployment environment. The
// combination of these two arguments determines what can be added to
// the view, as follows:
// * Global scope: All deployment nodes, infrastructure nodes, and
// container instances within the deployment environment.
// * Software system scope: All deployment nodes and infrastructure
// nodes within the deployment environment. Container instances within
// the deployment environment that belong to the software system.
DeploymentView(Global, "<environment name>", "[key]", "[description]", func() {
// ... same usage as SystemLandscape without EnterpriseBoundaryVisible.
})
// DeploymentView on a software system uses the software system as first
// argument.
DeploymentView(SoftwareSystem, "<environment name>", "[key]", "[description]", func() {
// see usage above
})
// Styles is a wrapper for one or more element/relationship styles,
// which are used when rendering diagrams.
Styles(func() {
// ElementStyle defines an element style. All nested properties
// (shape, icon, etc) are optional, see Structurizr - Notation for
// more details.
ElementStyle("<tag>", func() {
Shape(ShapeBox) // ShapeBox, ShapeRoundedBox, ShapeCircle, ShapeEllipse,
// ShapeHexagon, ShapeCylinder, ShapePipe, ShapePerson
// ShapeRobot, ShapeFolder, ShapeWebBrowser,
// ShapeMobileDevicePortrait, ShapeMobileDeviceLandscape,
// ShapeComponent.
Icon("<file>")
Width(42)
Height(42)
Background("#<rrggbb>")
Color("#<rrggbb>")
Stroke("#<rrggbb>")
FontSize(42)
Border(BorderSolid) // BorderSolid, BorderDashed, BorderDotted
Opacity(42) // Between 0 and 100
ShowMetadata()
ShowDescription()
})
// RelationshipStyle defines a relationship style. All nested
// properties (thickness, color, etc) are optional, see Structurizr
// - Notation for more details.
RelationshipStyle("<tag>", func() {
Thickness(42)
Color("#<rrggbb>")
Solid()
Routing(RoutingDirect) // RoutingDirect, RoutingOrthogonal, RoutingCurved
FontSize(42)
Width(42)
Position(42) // Between 0 and 100
Opacity(42) // Between 0 and 100
})
})
// Theme specifies one or more themes that should be used when
// rendering diagrams. See Structurizr - Themes for more details.
Theme("<theme URL>", "[theme URL]") // as many theme URLs as needed
// Branding defines custom branding that should be used when rendering
// diagrams and documentation. See Structurizr - Branding for more
// details.
Branding(func() {
Logo("<file>")
Font("<name>", "[url]")
})
})
})
Directories
¶
Path | Synopsis |
---|---|
cmd
|
|
Package dsl implements a Go based DSL that makes it possible to describe softare architecture models following the C4 model (https://c4model.com).
|
Package dsl implements a Go based DSL that makes it possible to describe softare architecture models following the C4 model (https://c4model.com). |
Package eval allows evaluating Go code that describes a software architecture model using the DSL defined in Structurizr for Go: https://github.com/goadesign/structurizr.
|
Package eval allows evaluating Go code that describes a software architecture model using the DSL defined in Structurizr for Go: https://github.com/goadesign/structurizr. |
examples
|
|
nested/styles
Package styles provide shared styles used by multiple models.
|
Package styles provide shared styles used by multiple models. |
Package service implements a client for the [Structurizr service HTTP APIs](https://structurizr.com/).
|
Package service implements a client for the [Structurizr service HTTP APIs](https://structurizr.com/). |