├── examples ├── logo.png ├── extend │ ├── 1.dsl │ ├── 2.dsl │ ├── 3.dsl │ ├── 4.dsl │ ├── workspace.dsl │ ├── extend-workspace-from-dsl-file.dsl │ ├── extend-workspace-from-json-file.dsl │ ├── extend-workspace-from-dsl-url.dsl │ ├── extend-workspace-from-json-url.dsl │ └── workspace.json ├── utf8.dsl ├── big-bank-plc │ ├── model │ │ ├── internet-banking-system │ │ │ ├── summary.dsl │ │ │ ├── docs │ │ │ │ ├── 04-deployment.adoc │ │ │ │ ├── 03-development-environment.adoc │ │ │ │ ├── 01-context.md │ │ │ │ └── 02-containers.md │ │ │ ├── adrs │ │ │ │ └── 0001-record-architecture-decisions.md │ │ │ └── details.dsl │ │ └── people-and-software-systems.dsl │ ├── views │ │ └── styles-people.dsl │ └── system-landscape.dsl ├── include │ └── model.dsl ├── getting-started.dsl ├── getting-started-short.dsl ├── ref.dsl ├── dynamic.dsl ├── include-from-file.dsl ├── include-from-url.dsl ├── parallel.dsl ├── hierarchical-identifiers-and-deployment-nodes.dsl ├── filteredviews.dsl ├── groups.dsl ├── deployment-groups.dsl └── amazon-web-services.dsl ├── docs ├── cookbook │ ├── introduction │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── example.dsl │ ├── themes │ │ ├── example-1.png │ │ ├── example-2.png │ │ ├── example-1.dsl │ │ └── example-2.dsl │ ├── dynamic-view │ │ ├── example-1.png │ │ ├── example-1.dsl │ │ └── README.md │ ├── component-view │ │ ├── example-1.png │ │ ├── example-1.dsl │ │ └── README.md │ ├── container-view │ │ ├── example-1.png │ │ ├── example-1.dsl │ │ └── README.md │ ├── deployment-view │ │ ├── example-1.png │ │ ├── example-1.dsl │ │ └── README.md │ ├── element-styles │ │ ├── example-1.png │ │ ├── example-2.png │ │ ├── example-3.png │ │ ├── example-1.dsl │ │ ├── example-2.dsl │ │ ├── example-3.dsl │ │ └── README.md │ ├── amazon-web-services │ │ ├── example-1.png │ │ ├── example-2.png │ │ ├── example-3.png │ │ ├── example-1.dsl │ │ ├── example-2.dsl │ │ └── example-3.dsl │ ├── relationship-styles │ │ ├── example-1.png │ │ ├── example-2.png │ │ ├── example-3.png │ │ ├── example-1.dsl │ │ ├── example-2.dsl │ │ ├── example-3.dsl │ │ └── README.md │ ├── system-context-view │ │ ├── example-1.png │ │ ├── example-1.dsl │ │ └── README.md │ ├── implied-relationships │ │ ├── example-1.png │ │ ├── example-2.png │ │ ├── example-3.png │ │ ├── example-4.png │ │ ├── example-1.dsl │ │ ├── example-3.dsl │ │ ├── example-2.dsl │ │ └── example-4.dsl │ ├── workspace │ │ ├── example-1.dsl │ │ └── README.md │ └── README.md ├── images │ └── structurizr-banner.png └── changelog.md ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── gradlew.bat ├── src ├── main │ └── java │ │ └── com │ │ └── structurizr │ │ └── dsl │ │ ├── StylesDslContext.java │ │ ├── UsersDslContext.java │ │ ├── ViewsDslContext.java │ │ ├── CommentDslContext.java │ │ ├── TerminologyDslContext.java │ │ ├── WorkspaceDslContext.java │ │ ├── ConfigurationDslContext.java │ │ ├── IdentifierScope.java │ │ ├── StructurizrDslFormatterException.java │ │ ├── ComponentViewDslContext.java │ │ ├── ContainerViewDslContext.java │ │ ├── EnterpriseDslContext.java │ │ ├── StaticStructureElementInstanceDslContext.java │ │ ├── SystemContextViewDslContext.java │ │ ├── SystemLandscapeViewDslContext.java │ │ ├── ViewDslContext.java │ │ ├── BrandingDslContext.java │ │ ├── CustomViewDslContext.java │ │ ├── DynamicViewDslContext.java │ │ ├── DeploymentEnvironmentDslContext.java │ │ ├── ModelItemDslContext.java │ │ ├── Constant.java │ │ ├── StaticViewDslContext.java │ │ ├── CustomViewAnimationDslContext.java │ │ ├── StaticViewAnimationDslContext.java │ │ ├── GroupableElementDslContext.java │ │ ├── RelationshipStyleDslContext.java │ │ ├── DeploymentViewDslContext.java │ │ ├── ModelItemPropertiesDslContext.java │ │ ├── DynamicViewParallelSequenceDslContext.java │ │ ├── ModelItemPerspectivesDslContext.java │ │ ├── DeploymentViewAnimationDslContext.java │ │ ├── GroupableDslContext.java │ │ ├── ElementStyleDslContext.java │ │ ├── RelationshipDslContext.java │ │ ├── DeploymentNodeDslContext.java │ │ ├── GroupParser.java │ │ ├── PersonDslContext.java │ │ ├── InfrastructureNodeDslContext.java │ │ ├── DeploymentGroup.java │ │ ├── ComponentDslContext.java │ │ ├── DeploymentGroupParser.java │ │ ├── CustomElementDslContext.java │ │ ├── IncludedDslContext.java │ │ ├── StructurizrDslParserException.java │ │ ├── DeploymentEnvironmentParser.java │ │ ├── EnterpriseParser.java │ │ ├── ContainerDslContext.java │ │ ├── ContainerInstanceDslContext.java │ │ ├── StructurizrDslExpressions.java │ │ ├── SoftwareSystemInstanceDslContext.java │ │ ├── SoftwareSystemDslContext.java │ │ ├── DslParserContext.java │ │ ├── RefParser.java │ │ ├── Tokens.java │ │ ├── IdentifierScopeParser.java │ │ ├── DslUtils.java │ │ ├── ElementGroup.java │ │ ├── ConstantParser.java │ │ ├── DeploymentEnvironment.java │ │ ├── ViewParser.java │ │ ├── UserRoleParser.java │ │ ├── ModelDslContext.java │ │ ├── FileUtils.java │ │ ├── ThemeParser.java │ │ ├── SystemLandscapeViewParser.java │ │ ├── AbstractParser.java │ │ ├── CustomViewParser.java │ │ ├── CustomViewExpressionParser.java │ │ ├── ViewContentParser.java │ │ ├── StaticViewAnimationStepParser.java │ │ ├── CustomViewAnimationStepParser.java │ │ ├── CustomElementParser.java │ │ ├── InfrastructureNodeParser.java │ │ ├── PersonParser.java │ │ ├── ImplicitRelationshipParser.java │ │ ├── IncludeParser.java │ │ ├── AbstractRelationshipParser.java │ │ ├── SoftwareSystemParser.java │ │ ├── ComponentParser.java │ │ ├── ContainerParser.java │ │ ├── AdrsParser.java │ │ ├── DocsParser.java │ │ ├── ContainerInstanceParser.java │ │ ├── ComponentViewParser.java │ │ ├── DeploymentViewAnimationStepParser.java │ │ ├── BrandingParser.java │ │ ├── SoftwareSystemInstanceParser.java │ │ ├── ContainerViewParser.java │ │ ├── SystemContextViewParser.java │ │ ├── HealthCheckParser.java │ │ ├── ImpliedRelationshipsParser.java │ │ ├── ModelItemParser.java │ │ ├── DeploymentNodeParser.java │ │ ├── StaticViewExpressionParser.java │ │ └── DslContext.java └── test │ └── java │ └── com │ └── structurizr │ └── dsl │ ├── AdrsParserTests.java │ ├── DocsParserTests.java │ ├── AbstractTests.java │ ├── IncludeParserTests.java │ ├── CustomViewAnimationStepParserTests.java │ ├── StaticViewAnimationStepParserTests.java │ ├── GroupParserTests.java │ ├── DeploymentViewAnimationStepParserTests.java │ ├── DeploymentGroupParserTests.java │ ├── EnterpriseParserTests.java │ ├── DeploymentEnvironmentParserTests.java │ ├── IdentifierScopeParserTests.java │ ├── RefParserTests.java │ ├── ConstantParserTests.java │ ├── SystemLandscapeViewParserTests.java │ └── UserRoleParserTests.java ├── gradle.properties ├── .gitignore ├── .github └── workflows │ └── gradle.yml └── gradlew.bat /examples/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/examples/logo.png -------------------------------------------------------------------------------- /docs/cookbook/introduction/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/introduction/1.png -------------------------------------------------------------------------------- /docs/cookbook/introduction/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/introduction/2.png -------------------------------------------------------------------------------- /docs/cookbook/introduction/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/introduction/3.png -------------------------------------------------------------------------------- /docs/cookbook/introduction/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/introduction/4.png -------------------------------------------------------------------------------- /examples/extend/1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | user1 = person "User 1" 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /examples/utf8.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | user = person "你好 Usér 🙂" 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/cookbook/themes/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/themes/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/themes/example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/themes/example-2.png -------------------------------------------------------------------------------- /docs/images/structurizr-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/images/structurizr-banner.png -------------------------------------------------------------------------------- /docs/cookbook/dynamic-view/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/dynamic-view/example-1.png -------------------------------------------------------------------------------- /examples/big-bank-plc/model/internet-banking-system/summary.dsl: -------------------------------------------------------------------------------- 1 | url https://structurizr.com/share/36141/diagrams#SystemContext -------------------------------------------------------------------------------- /examples/extend/2.dsl: -------------------------------------------------------------------------------- 1 | workspace extends 1.dsl { 2 | 3 | model { 4 | user2 = person "User 2" 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /examples/extend/3.dsl: -------------------------------------------------------------------------------- 1 | workspace extends 2.dsl { 2 | 3 | model { 4 | user3 = person "User 3" 5 | } 6 | 7 | } -------------------------------------------------------------------------------- /docs/cookbook/component-view/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/component-view/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/container-view/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/container-view/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/deployment-view/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/deployment-view/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/element-styles/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/element-styles/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/element-styles/example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/element-styles/example-2.png -------------------------------------------------------------------------------- /docs/cookbook/element-styles/example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/element-styles/example-3.png -------------------------------------------------------------------------------- /docs/cookbook/amazon-web-services/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/amazon-web-services/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/amazon-web-services/example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/amazon-web-services/example-2.png -------------------------------------------------------------------------------- /docs/cookbook/amazon-web-services/example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/amazon-web-services/example-3.png -------------------------------------------------------------------------------- /docs/cookbook/relationship-styles/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/relationship-styles/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/relationship-styles/example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/relationship-styles/example-2.png -------------------------------------------------------------------------------- /docs/cookbook/relationship-styles/example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/relationship-styles/example-3.png -------------------------------------------------------------------------------- /docs/cookbook/system-context-view/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/system-context-view/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/implied-relationships/example-1.png -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/implied-relationships/example-2.png -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/implied-relationships/example-3.png -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/buger/dsl/master/docs/cookbook/implied-relationships/example-4.png -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StylesDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class StylesDslContext extends DslContext { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/UsersDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class UsersDslContext extends DslContext { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ViewsDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class ViewsDslContext extends DslContext { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CommentDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class CommentDslContext extends DslContext { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/TerminologyDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class TerminologyDslContext extends DslContext { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/WorkspaceDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class WorkspaceDslContext extends DslContext { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ConfigurationDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class ConfigurationDslContext extends DslContext { 4 | } -------------------------------------------------------------------------------- /docs/cookbook/workspace/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace "Name" "Description" { 2 | 3 | model { 4 | } 5 | 6 | views { 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/IdentifierScope.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | public enum IdentifierScope { 4 | 5 | Flat, 6 | Hierarchical, 7 | 8 | } -------------------------------------------------------------------------------- /examples/extend/4.dsl: -------------------------------------------------------------------------------- 1 | workspace extends 3.dsl { 2 | 3 | views { 4 | systemLandscape { 5 | include user1 user2 user3 6 | } 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | signing.keyId=123456768 2 | signing.password=password 3 | signing.secretKeyRingFile=/some/path 4 | 5 | ossrhUsername=username 6 | ossrhPassword=password 7 | 8 | -------------------------------------------------------------------------------- /examples/include/model.dsl: -------------------------------------------------------------------------------- 1 | user = person "User" "A user of my software system." 2 | softwareSystem = softwareSystem "Software System" "My software system, code-named \"X\"." 3 | 4 | user -> softwareSystem "Uses" -------------------------------------------------------------------------------- /examples/big-bank-plc/model/internet-banking-system/docs/04-deployment.adoc: -------------------------------------------------------------------------------- 1 | == Deployment 2 | 3 | Here is some information about the live deployment environment for the Internet Banking System... 4 | 5 | image::embed:LiveDeployment[] -------------------------------------------------------------------------------- /examples/big-bank-plc/views/styles-people.dsl: -------------------------------------------------------------------------------- 1 | element "Person" { 2 | color #ffffff 3 | fontSize 22 4 | shape Person 5 | } 6 | element "Customer" { 7 | background #08427b 8 | } 9 | element "Bank Staff" { 10 | background #999999 11 | } -------------------------------------------------------------------------------- /examples/big-bank-plc/model/internet-banking-system/docs/03-development-environment.adoc: -------------------------------------------------------------------------------- 1 | == Development Environment 2 | 3 | Here is some information about how to set up a development environment for the Internet Banking System... 4 | 5 | image::embed:DevelopmentDeployment[] -------------------------------------------------------------------------------- /examples/big-bank-plc/model/internet-banking-system/docs/01-context.md: -------------------------------------------------------------------------------- 1 | ## Context 2 | 3 | Here is some context about the Internet Banking System... 4 | 5 | ![](embed:SystemContext) 6 | 7 | ### Internet Banking System 8 | ... 9 | 10 | ### Mainframe Banking System 11 | ... 12 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StructurizrDslFormatterException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | public final class StructurizrDslFormatterException extends Exception { 4 | 5 | StructurizrDslFormatterException(String message) { 6 | super(message); 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 10 08:57:01 GMT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip 7 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ComponentViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.ComponentView; 4 | 5 | final class ComponentViewDslContext extends StaticViewDslContext { 6 | 7 | ComponentViewDslContext(ComponentView view) { 8 | super(view); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ContainerViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.ContainerView; 4 | 5 | final class ContainerViewDslContext extends StaticViewDslContext { 6 | 7 | ContainerViewDslContext(ContainerView view) { 8 | super(view); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /examples/extend/workspace.dsl: -------------------------------------------------------------------------------- 1 | workspace "Getting Started" "This is a model of my software system." { 2 | 3 | model { 4 | user = person "User" "A user of my software system." 5 | softwareSystem = softwareSystem "Software System" "My software system." 6 | 7 | user -> softwareSystem "Uses" 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/EnterpriseDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class EnterpriseDslContext extends GroupableDslContext { 4 | 5 | EnterpriseDslContext() { 6 | super(); 7 | } 8 | 9 | EnterpriseDslContext(ElementGroup group) { 10 | super(group); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StaticStructureElementInstanceDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.StaticStructureElementInstance; 4 | 5 | abstract class StaticStructureElementInstanceDslContext extends ModelItemDslContext { 6 | 7 | abstract StaticStructureElementInstance getElementInstance(); 8 | 9 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SystemContextViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.SystemContextView; 4 | 5 | final class SystemContextViewDslContext extends StaticViewDslContext { 6 | 7 | SystemContextViewDslContext(SystemContextView view) { 8 | super(view); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /docs/cookbook/system-context-view/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" 6 | 7 | u -> s "Uses" 8 | } 9 | 10 | views { 11 | systemContext s { 12 | include * 13 | autoLayout lr 14 | } 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SystemLandscapeViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.SystemLandscapeView; 4 | 5 | final class SystemLandscapeViewDslContext extends StaticViewDslContext { 6 | 7 | SystemLandscapeViewDslContext(SystemLandscapeView view) { 8 | super(view); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.View; 4 | 5 | class ViewDslContext extends DslContext { 6 | 7 | private View view; 8 | 9 | ViewDslContext(View view) { 10 | this.view = view; 11 | } 12 | 13 | View getView() { 14 | return view; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/BrandingDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import java.io.File; 4 | 5 | final class BrandingDslContext extends DslContext { 6 | 7 | private File file; 8 | 9 | BrandingDslContext(File file) { 10 | this.file = file; 11 | } 12 | 13 | File getFile() { 14 | return file; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /docs/cookbook/element-styles/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | a = softwareSystem "A" 5 | b = softwareSystem "B" 6 | c = softwareSystem "C" 7 | 8 | a -> b 9 | b -> c 10 | } 11 | 12 | views { 13 | systemLandscape { 14 | include * 15 | autolayout lr 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /docs/cookbook/relationship-styles/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | a = softwareSystem "A" 5 | b = softwareSystem "B" 6 | c = softwareSystem "C" 7 | 8 | a -> b 9 | b -> c 10 | } 11 | 12 | views { 13 | systemLandscape { 14 | include * 15 | autolayout lr 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /examples/getting-started.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | user = person "User" 5 | softwareSystem = softwareSystem "Software System" 6 | 7 | user -> softwareSystem "Uses" 8 | } 9 | 10 | views { 11 | systemContext softwareSystem { 12 | include * 13 | autolayout 14 | } 15 | 16 | theme default 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CustomViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.CustomView; 4 | 5 | final class CustomViewDslContext extends ViewDslContext { 6 | 7 | CustomViewDslContext(CustomView view) { 8 | super(view); 9 | } 10 | 11 | public CustomView getCustomView() { 12 | return (CustomView)super.getView(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DynamicViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.DynamicView; 4 | 5 | class DynamicViewDslContext extends DslContext { 6 | 7 | private DynamicView view; 8 | 9 | DynamicViewDslContext(DynamicView view) { 10 | this.view = view; 11 | } 12 | 13 | DynamicView getView() { 14 | return view; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | .gradle 25 | .idea 26 | build 27 | out 28 | -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" 7 | } 8 | 9 | u -> webapp "Uses" 10 | } 11 | 12 | views { 13 | systemContext s { 14 | include * 15 | autoLayout lr 16 | } 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentEnvironmentDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class DeploymentEnvironmentDslContext extends DslContext { 4 | 5 | private String environment; 6 | 7 | DeploymentEnvironmentDslContext(String environment) { 8 | this.environment = environment; 9 | } 10 | 11 | String getEnvironment() { 12 | return environment; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /examples/getting-started-short.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | user = person "User" "A user of my software system." 5 | softwareSystem = softwareSystem "Software System" "My software system." 6 | 7 | user -> softwareSystem "Uses" 8 | } 9 | 10 | views { 11 | systemContext softwareSystem { 12 | include * 13 | autoLayout 14 | } 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ModelItemDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.ModelItem; 4 | 5 | abstract class ModelItemDslContext extends GroupableDslContext { 6 | 7 | ModelItemDslContext() { 8 | super(); 9 | } 10 | 11 | ModelItemDslContext(ElementGroup group) { 12 | super(group); 13 | } 14 | 15 | abstract ModelItem getModelItem(); 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/Constant.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | class Constant { 4 | 5 | private String name; 6 | private String value; 7 | 8 | Constant(String name, String value) { 9 | this.name = name; 10 | this.value = value; 11 | } 12 | 13 | String getName() { 14 | return name; 15 | } 16 | 17 | String getValue() { 18 | return value; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StaticViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.StaticView; 4 | 5 | class StaticViewDslContext extends ViewDslContext { 6 | 7 | private StaticView view; 8 | 9 | StaticViewDslContext(StaticView view) { 10 | super(view); 11 | 12 | this.view = view; 13 | } 14 | 15 | StaticView getView() { 16 | return view; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-3.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" 7 | } 8 | 9 | u -> webapp "Uses 1" 10 | u -> webapp "Uses 2" 11 | } 12 | 13 | views { 14 | systemContext s { 15 | include * 16 | autoLayout lr 17 | } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CustomViewAnimationDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.CustomView; 4 | 5 | class CustomViewAnimationDslContext extends DslContext { 6 | 7 | private CustomView view; 8 | 9 | CustomViewAnimationDslContext(CustomView view) { 10 | super(); 11 | 12 | this.view = view; 13 | } 14 | 15 | CustomView getView() { 16 | return view; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StaticViewAnimationDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.StaticView; 4 | 5 | class StaticViewAnimationDslContext extends DslContext { 6 | 7 | private StaticView view; 8 | 9 | StaticViewAnimationDslContext(StaticView view) { 10 | super(); 11 | 12 | this.view = view; 13 | } 14 | 15 | StaticView getView() { 16 | return view; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /docs/cookbook/themes/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | softwareSystem "Authentication Service" { 5 | tags "Microsoft Azure - Azure Active Directory" 6 | } 7 | } 8 | 9 | views { 10 | systemLandscape { 11 | include * 12 | autoLayout lr 13 | } 14 | 15 | theme https://static.structurizr.com/themes/microsoft-azure-2021.01.26/theme.json 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-2.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | impliedRelationships false 5 | 6 | u = person "User" 7 | s = softwareSystem "Software System" { 8 | webapp = container "Web Application" 9 | } 10 | 11 | u -> webapp "Uses" 12 | } 13 | 14 | views { 15 | systemContext s { 16 | include u s 17 | autoLayout 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/GroupableElementDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.GroupableElement; 4 | 5 | abstract class GroupableElementDslContext extends ModelItemDslContext { 6 | 7 | GroupableElementDslContext() { 8 | super(); 9 | } 10 | 11 | GroupableElementDslContext(ElementGroup group) { 12 | super(group); 13 | } 14 | 15 | abstract GroupableElement getElement(); 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/RelationshipStyleDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.RelationshipStyle; 4 | 5 | final class RelationshipStyleDslContext extends DslContext { 6 | 7 | private RelationshipStyle style; 8 | 9 | RelationshipStyleDslContext(RelationshipStyle style) { 10 | this.style = style; 11 | } 12 | 13 | RelationshipStyle getStyle() { 14 | return style; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentViewDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.DeploymentView; 4 | 5 | final class DeploymentViewDslContext extends ViewDslContext { 6 | 7 | private DeploymentView view; 8 | 9 | DeploymentViewDslContext(DeploymentView view) { 10 | super(view); 11 | 12 | this.view = view; 13 | } 14 | 15 | DeploymentView getView() { 16 | return view; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ModelItemPropertiesDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.ModelItem; 4 | 5 | final class ModelItemPropertiesDslContext extends DslContext { 6 | 7 | private ModelItem modelItem; 8 | 9 | public ModelItemPropertiesDslContext(ModelItem modelItem) { 10 | this.modelItem = modelItem; 11 | } 12 | 13 | ModelItem getModelItem() { 14 | return this.modelItem; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DynamicViewParallelSequenceDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class DynamicViewParallelSequenceDslContext extends DynamicViewDslContext { 4 | 5 | DynamicViewParallelSequenceDslContext(DynamicViewDslContext context) { 6 | super(context.getView()); 7 | getView().startParallelSequence(); 8 | } 9 | 10 | @Override 11 | void end() { 12 | getView().endParallelSequence(); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ModelItemPerspectivesDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.ModelItem; 4 | 5 | final class ModelItemPerspectivesDslContext extends DslContext { 6 | 7 | private ModelItem modelItem; 8 | 9 | public ModelItemPerspectivesDslContext(ModelItem modelItem) { 10 | this.modelItem = modelItem; 11 | } 12 | 13 | ModelItem getModelItem() { 14 | return this.modelItem; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentViewAnimationDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.DeploymentView; 4 | 5 | class DeploymentViewAnimationDslContext extends DslContext { 6 | 7 | private DeploymentView view; 8 | 9 | DeploymentViewAnimationDslContext(DeploymentView view) { 10 | super(); 11 | 12 | this.view = view; 13 | } 14 | 15 | DeploymentView getView() { 16 | return view; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /docs/cookbook/container-view/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" 7 | database = container "Database" 8 | } 9 | 10 | u -> webapp "Uses" 11 | webapp -> database "Reads from and writes to" 12 | } 13 | 14 | views { 15 | container s { 16 | include * 17 | autoLayout lr 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /docs/cookbook/implied-relationships/example-4.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" 7 | } 8 | 9 | u -> s "Uses 1" 10 | u -> s "Uses 2" 11 | u -> webapp "Uses 1" 12 | u -> webapp "Uses 2" 13 | } 14 | 15 | views { 16 | systemContext s { 17 | include * 18 | autoLayout lr 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/GroupableDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | abstract class GroupableDslContext extends DslContext { 4 | 5 | private ElementGroup group; 6 | 7 | GroupableDslContext() { 8 | this(null); 9 | } 10 | 11 | GroupableDslContext(ElementGroup group) { 12 | this.group = group; 13 | } 14 | 15 | boolean hasGroup() { 16 | return group != null; 17 | } 18 | 19 | ElementGroup getGroup() { 20 | return group; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /examples/big-bank-plc/model/internet-banking-system/docs/02-containers.md: -------------------------------------------------------------------------------- 1 | ## Software Architecture 2 | 3 | Here is some information about the software architecture of the Internet Banking System... 4 | 5 | ![](embed:Containers) 6 | 7 | ### Web Application 8 | ... 9 | 10 | ### Database 11 | ... 12 | 13 | Here is some information about the API Application... 14 | 15 | ![](embed:Components) 16 | 17 | ### Sign in process 18 | 19 | Here is some information about the Sign In Controller, including how the sign in process works... 20 | 21 | ![](embed:SignIn) -------------------------------------------------------------------------------- /docs/cookbook/relationship-styles/example-2.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | a = softwareSystem "A" 5 | b = softwareSystem "B" 6 | c = softwareSystem "C" 7 | 8 | a -> b 9 | b -> c 10 | } 11 | 12 | views { 13 | systemLandscape { 14 | include * 15 | autolayout lr 16 | } 17 | 18 | styles { 19 | relationship "Relationship" { 20 | color #ff0000 21 | dashed false 22 | } 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /docs/cookbook/element-styles/example-2.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | a = softwareSystem "A" 5 | b = softwareSystem "B" 6 | c = softwareSystem "C" 7 | 8 | a -> b 9 | b -> c 10 | } 11 | 12 | views { 13 | systemLandscape { 14 | include * 15 | autolayout lr 16 | } 17 | 18 | styles { 19 | element "Element" { 20 | background #1168bd 21 | color #ffffff 22 | shape RoundedBox 23 | } 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ElementStyleDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.ElementStyle; 4 | 5 | import java.io.File; 6 | 7 | final class ElementStyleDslContext extends DslContext { 8 | 9 | private File file; 10 | private ElementStyle style; 11 | 12 | ElementStyleDslContext(ElementStyle style, File file) { 13 | this.style = style; 14 | this.file = file; 15 | } 16 | 17 | File getFile() { 18 | return file; 19 | } 20 | 21 | ElementStyle getStyle() { 22 | return style; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /docs/cookbook/relationship-styles/example-3.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | a = softwareSystem "A" 5 | b = softwareSystem "B" 6 | c = softwareSystem "C" 7 | 8 | a -> b 9 | b -> c { 10 | tags "Tag 1" 11 | } 12 | } 13 | 14 | views { 15 | systemLandscape { 16 | include * 17 | autolayout lr 18 | } 19 | 20 | styles { 21 | relationship "Tag 1" { 22 | color #ff0000 23 | dashed false 24 | } 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /examples/ref.dsl: -------------------------------------------------------------------------------- 1 | workspace extends https://raw.githubusercontent.com/structurizr/dsl/master/examples/amazon-web-services.dsl { 2 | 3 | model { 4 | 5 | region = ref "DeploymentNode://Live/Amazon Web Services/US-East-1" { 6 | deploymentNode "New deployment node" { 7 | infrastructureNode "New infrastructure node" { 8 | -> route53 9 | } 10 | } 11 | } 12 | 13 | } 14 | 15 | views { 16 | deployment * "Live" { 17 | include region 18 | autolayout lr 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/RelationshipDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.ModelItem; 4 | import com.structurizr.model.Relationship; 5 | 6 | final class RelationshipDslContext extends ModelItemDslContext { 7 | 8 | private Relationship relationship; 9 | 10 | RelationshipDslContext(Relationship relationship) { 11 | this.relationship = relationship; 12 | } 13 | 14 | Relationship getRelationship() { 15 | return relationship; 16 | } 17 | 18 | @Override 19 | ModelItem getModelItem() { 20 | return getRelationship(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /docs/cookbook/element-styles/example-3.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | a = softwareSystem "A" { 5 | tags "Tag 1" 6 | } 7 | b = softwareSystem "B" 8 | c = softwareSystem "C" 9 | 10 | a -> b 11 | b -> c 12 | } 13 | 14 | views { 15 | systemLandscape { 16 | include * 17 | autolayout lr 18 | } 19 | 20 | styles { 21 | element "Tag 1" { 22 | background #1168bd 23 | color #ffffff 24 | shape RoundedBox 25 | } 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /examples/big-bank-plc/model/internet-banking-system/adrs/0001-record-architecture-decisions.md: -------------------------------------------------------------------------------- 1 | # 1. Record architecture decisions 2 | 3 | Date: 2020-06-05 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | We need to record the architectural decisions made on this project. 12 | 13 | ## Decision 14 | 15 | We will use Architecture Decision Records, as described by Michael Nygard in this article: [http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) 16 | 17 | ## Consequences 18 | 19 | See Michael Nygard's article, linked above. -------------------------------------------------------------------------------- /examples/big-bank-plc/system-landscape.dsl: -------------------------------------------------------------------------------- 1 | !constant INTERNET_BANKING_SYSTEM_INCLUDE "summary.dsl" 2 | 3 | workspace "Big Bank plc - System Landscape" "The system landscape for Big Bank plc." { 4 | 5 | model { 6 | !include model/people-and-software-systems.dsl 7 | } 8 | 9 | views { 10 | systemlandscape "SystemLandscape" { 11 | include * 12 | } 13 | 14 | styles { 15 | !include views/styles-people.dsl 16 | 17 | element "Software System" { 18 | background #999999 19 | color #ffffff 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /docs/cookbook/themes/example-2.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | softwareSystem "Authentication Service" { 5 | tags "Microsoft Azure - Azure Active Directory" 6 | } 7 | } 8 | 9 | views { 10 | systemLandscape { 11 | include * 12 | autoLayout lr 13 | } 14 | 15 | styles { 16 | element "Software System" { 17 | background #ffffff 18 | shape RoundedBox 19 | } 20 | } 21 | 22 | theme https://static.structurizr.com/themes/microsoft-azure-2021.01.26/theme.json 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /examples/dynamic.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | a = softwareSystem "A" 5 | b = softwareSystem "B" 6 | 7 | a -> b "Sends data to" 8 | } 9 | 10 | views { 11 | dynamic * { 12 | // with this example, the relationship uses the same description as defined in the static model 13 | a -> b 14 | autoLayout 15 | } 16 | 17 | dynamic * { 18 | // with this example, the relationship description is overriden to describe a particular feature/use case/etc 19 | a -> b "Sends customer data to" 20 | autoLayout 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentNodeDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.DeploymentNode; 4 | import com.structurizr.model.ModelItem; 5 | 6 | final class DeploymentNodeDslContext extends ModelItemDslContext { 7 | 8 | private DeploymentNode deploymentNode; 9 | 10 | DeploymentNodeDslContext(DeploymentNode deploymentNode) { 11 | this.deploymentNode = deploymentNode; 12 | } 13 | 14 | DeploymentNode getDeploymentNode() { 15 | return deploymentNode; 16 | } 17 | 18 | @Override 19 | ModelItem getModelItem() { 20 | return getDeploymentNode(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /docs/cookbook/component-view/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" { 7 | c1 = component "Component 1" 8 | c2 = component "Component 2" 9 | } 10 | database = container "Database" 11 | } 12 | 13 | u -> c1 "Uses" 14 | c1 -> c2 "Uses" 15 | c2 -> database "Reads from and writes to" 16 | } 17 | 18 | views { 19 | component webapp { 20 | include * 21 | autoLayout lr 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/GroupParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | class GroupParser { 4 | 5 | private static final String GRAMMAR = "group {"; 6 | 7 | private final static int NAME_INDEX = 1; 8 | 9 | ElementGroup parse(Tokens tokens) { 10 | // group 11 | 12 | if (tokens.hasMoreThan(NAME_INDEX)) { 13 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 14 | } 15 | 16 | if (!tokens.includes(NAME_INDEX)) { 17 | throw new RuntimeException("Expected: " + GRAMMAR); 18 | } 19 | 20 | return new ElementGroup(tokens.get(NAME_INDEX)); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/PersonDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.GroupableElement; 4 | import com.structurizr.model.ModelItem; 5 | import com.structurizr.model.Person; 6 | 7 | final class PersonDslContext extends GroupableElementDslContext { 8 | 9 | private Person person; 10 | 11 | PersonDslContext(Person person) { 12 | this.person = person; 13 | } 14 | 15 | Person getPerson() { 16 | return person; 17 | } 18 | 19 | @Override 20 | ModelItem getModelItem() { 21 | return getPerson(); 22 | } 23 | 24 | @Override 25 | GroupableElement getElement() { 26 | return person; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /examples/include-from-file.dsl: -------------------------------------------------------------------------------- 1 | workspace "Getting Started" "This is a model of my software system." { 2 | 3 | model { 4 | !include include/model.dsl 5 | } 6 | 7 | views { 8 | systemContext softwareSystem "SystemContext" "An example of a System Context diagram." { 9 | include * 10 | autoLayout 11 | } 12 | 13 | styles { 14 | element "Software System" { 15 | background #1168bd 16 | color #ffffff 17 | } 18 | element "Person" { 19 | shape person 20 | background #08427b 21 | color #ffffff 22 | } 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/InfrastructureNodeDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.InfrastructureNode; 4 | import com.structurizr.model.ModelItem; 5 | 6 | final class InfrastructureNodeDslContext extends ModelItemDslContext { 7 | 8 | private InfrastructureNode infrastructureNode; 9 | 10 | InfrastructureNodeDslContext(InfrastructureNode infrastructureNode) { 11 | this.infrastructureNode = infrastructureNode; 12 | } 13 | 14 | InfrastructureNode getInfrastructureNode() { 15 | return infrastructureNode; 16 | } 17 | 18 | @Override 19 | ModelItem getModelItem() { 20 | return getInfrastructureNode(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /docs/cookbook/introduction/example.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | clinicEmployee = person "Clinic Employee" 5 | springPetClinic = softwareSystem "Spring PetClinic"{ 6 | webApplication = container "Web Application" 7 | database = container "Database" 8 | } 9 | 10 | clinicEmployee -> webApplication "Uses" 11 | webApplication -> database "Reads from and writes to" 12 | } 13 | 14 | views { 15 | systemContext springPetClinic { 16 | include * 17 | autolayout 18 | } 19 | 20 | container springPetClinic { 21 | include * 22 | autolayout 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentGroup.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Element; 4 | 5 | import java.util.Set; 6 | 7 | class DeploymentGroup extends Element { 8 | 9 | private String name; 10 | 11 | DeploymentGroup(String name) { 12 | this.name = name; 13 | } 14 | 15 | @Override 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | @Override 21 | public String getCanonicalName() { 22 | return name; 23 | } 24 | 25 | @Override 26 | public Element getParent() { 27 | return null; 28 | } 29 | 30 | @Override 31 | protected Set getRequiredTags() { 32 | return null; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/AdrsParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class AdrsParserTests extends AbstractTests { 9 | 10 | private AdrsParser parser = new AdrsParser(); 11 | 12 | @Test 13 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 14 | try { 15 | parser.parse(new WorkspaceDslContext(), null, tokens("adrs", "path", "extra")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Too many tokens, expected: !adrs ", e.getMessage()); 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/DocsParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class DocsParserTests extends AbstractTests { 9 | 10 | private DocsParser parser = new DocsParser(); 11 | 12 | @Test 13 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 14 | try { 15 | parser.parse(new WorkspaceDslContext(), null, tokens("docs", "path", "extra")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Too many tokens, expected: !docs ", e.getMessage()); 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ComponentDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Component; 4 | import com.structurizr.model.GroupableElement; 5 | import com.structurizr.model.ModelItem; 6 | 7 | final class ComponentDslContext extends GroupableElementDslContext { 8 | 9 | private Component component; 10 | 11 | ComponentDslContext(Component component) { 12 | this.component = component; 13 | } 14 | 15 | Component getComponent() { 16 | return component; 17 | } 18 | 19 | @Override 20 | ModelItem getModelItem() { 21 | return getComponent(); 22 | } 23 | 24 | @Override 25 | GroupableElement getElement() { 26 | return component; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /examples/include-from-url.dsl: -------------------------------------------------------------------------------- 1 | workspace "Getting Started" "This is a model of my software system." { 2 | 3 | model { 4 | !include https://raw.githubusercontent.com/structurizr/dsl/master/examples/include/model.dsl 5 | } 6 | 7 | views { 8 | systemContext softwareSystem "SystemContext" "An example of a System Context diagram." { 9 | include * 10 | autoLayout 11 | } 12 | 13 | styles { 14 | element "Software System" { 15 | background #1168bd 16 | color #ffffff 17 | } 18 | element "Person" { 19 | shape person 20 | background #08427b 21 | color #ffffff 22 | } 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v2 21 | with: 22 | java-version: '11' 23 | distribution: 'adopt' 24 | - name: Grant execute permission for gradlew 25 | run: chmod +x gradlew 26 | - name: Build with Gradle 27 | run: ./gradlew 28 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentGroupParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class DeploymentGroupParser extends AbstractParser { 4 | 5 | private static final String GRAMMAR = "deploymentGroup "; 6 | 7 | private static final int DEPLOYMENT_GROUP_NAME_INDEX = 1; 8 | 9 | String parse(Tokens tokens) { 10 | // deploymentGroup 11 | 12 | if (tokens.hasMoreThan(DEPLOYMENT_GROUP_NAME_INDEX)) { 13 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 14 | } else if (tokens.size() != DEPLOYMENT_GROUP_NAME_INDEX + 1) { 15 | throw new RuntimeException("Expected: " + GRAMMAR); 16 | } else { 17 | return tokens.get(DEPLOYMENT_GROUP_NAME_INDEX); 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CustomElementDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.CustomElement; 4 | import com.structurizr.model.GroupableElement; 5 | import com.structurizr.model.ModelItem; 6 | 7 | final class CustomElementDslContext extends GroupableElementDslContext { 8 | 9 | private CustomElement customElement; 10 | 11 | CustomElementDslContext(CustomElement customElement) { 12 | this.customElement = customElement; 13 | } 14 | 15 | CustomElement getCustomElement() { 16 | return customElement; 17 | } 18 | 19 | @Override 20 | ModelItem getModelItem() { 21 | return getCustomElement(); 22 | } 23 | 24 | @Override 25 | GroupableElement getElement() { 26 | return customElement; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/IncludedDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | 6 | final class IncludedDslContext extends DslContext { 7 | 8 | private List lines; 9 | private File parentFile; 10 | private File file; 11 | 12 | IncludedDslContext(File parentFile) { 13 | this.parentFile = parentFile; 14 | } 15 | 16 | List getLines() { 17 | return lines; 18 | } 19 | 20 | void setLines(List lines) { 21 | this.lines = lines; 22 | } 23 | 24 | File getParentFile() { 25 | return parentFile; 26 | } 27 | 28 | File getFile() { 29 | return file; 30 | } 31 | 32 | void setFile(File file) { 33 | this.file = file; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StructurizrDslParserException.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | public final class StructurizrDslParserException extends Exception { 4 | 5 | private int lineNumber; 6 | private String line; 7 | 8 | StructurizrDslParserException(String message) { 9 | super(message); 10 | } 11 | 12 | StructurizrDslParserException(String message, int lineNumber, String line) { 13 | super((message.endsWith(".") ? message.substring(0, message.length()-1) : message) + " at line " + lineNumber + ": " + line.trim()); 14 | this.lineNumber = lineNumber; 15 | this.line = line; 16 | } 17 | 18 | public int getLineNumber() { 19 | return lineNumber; 20 | } 21 | 22 | public String getLine() { 23 | return line; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentEnvironmentParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class DeploymentEnvironmentParser extends AbstractParser { 4 | 5 | private static final String GRAMMAR = "deploymentEnvironment {"; 6 | 7 | private static final int DEPLOYMENT_ENVIRONMENT_NAME_INDEX = 1; 8 | 9 | String parse(Tokens tokens) { 10 | // deploymentEnvironment 11 | 12 | if (tokens.hasMoreThan(DEPLOYMENT_ENVIRONMENT_NAME_INDEX)) { 13 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 14 | } else if (tokens.size() != DEPLOYMENT_ENVIRONMENT_NAME_INDEX + 1) { 15 | throw new RuntimeException("Expected: " + GRAMMAR); 16 | } else { 17 | return tokens.get(DEPLOYMENT_ENVIRONMENT_NAME_INDEX); 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /examples/extend/extend-workspace-from-dsl-file.dsl: -------------------------------------------------------------------------------- 1 | workspace extends workspace.dsl { 2 | name "A new name" 3 | description "A new description" 4 | 5 | model { 6 | softwareSystem = softwareSystem "Software System" { 7 | webapp = container "Web Application" 8 | } 9 | } 10 | 11 | views { 12 | systemContext softwareSystem "SystemContext" "An example of a System Context diagram." { 13 | include * 14 | autoLayout 15 | } 16 | 17 | styles { 18 | element "Software System" { 19 | background #1168bd 20 | color #ffffff 21 | } 22 | element "Person" { 23 | shape person 24 | background #08427b 25 | color #ffffff 26 | } 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /examples/extend/extend-workspace-from-json-file.dsl: -------------------------------------------------------------------------------- 1 | workspace extends workspace.json { 2 | name "A new name" 3 | description "A new description" 4 | 5 | model { 6 | softwareSystem = softwareSystem "Software System" { 7 | webapp = container "Web Application" 8 | } 9 | } 10 | 11 | views { 12 | systemContext softwareSystem "SystemContext" "An example of a System Context diagram." { 13 | include * 14 | autoLayout 15 | } 16 | 17 | styles { 18 | element "Software System" { 19 | background #1168bd 20 | color #ffffff 21 | } 22 | element "Person" { 23 | shape person 24 | background #08427b 25 | color #ffffff 26 | } 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /examples/parallel.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | user = person "User" 5 | softwareSystem = softwareSystem "Software System" { 6 | webapp = container "Web Application" 7 | bus = container "Message Bus" 8 | app1 = container "App 1" 9 | app2 = container "App 2" 10 | } 11 | 12 | user -> webapp "Updates details" 13 | webapp -> bus "Sends update event" 14 | bus -> app1 "Broadcasts update event" 15 | bus -> app2 "Broadcasts update event" 16 | } 17 | 18 | views { 19 | dynamic softwareSystem { 20 | user -> webapp 21 | webapp -> bus 22 | { 23 | bus -> app1 24 | } 25 | { 26 | bus -> app2 27 | } 28 | 29 | autoLayout 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/EnterpriseParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.model.Enterprise; 5 | 6 | final class EnterpriseParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "enterprise "; 9 | 10 | private static final int NAME_INDEX = 1; 11 | 12 | void parse(DslContext context, Tokens tokens) { 13 | Workspace workspace = context.getWorkspace(); 14 | 15 | if (tokens.hasMoreThan(NAME_INDEX)) { 16 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 17 | } else if (tokens.includes(NAME_INDEX)) { 18 | workspace.getModel().setEnterprise(new Enterprise(tokens.get(1))); 19 | } else { 20 | throw new RuntimeException("Expected: " + GRAMMAR); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /docs/cookbook/README.md: -------------------------------------------------------------------------------- 1 | # Structurizr DSL cookbook 2 | 3 | Creating software architecture diagrams from a textual definition is becoming more popular, but it's easy to introduce inconsistencies into your diagrams if you don't keep the multiple source files in sync. This cookbook is a guide to the Structurizr DSL, an open source tool for creating diagrams as code from a single consistent model. 4 | 5 | ## Table of contents 6 | 7 | - [Introduction](introduction) 8 | - [Workspace](workspace) 9 | - [System Context view](system-context-view) 10 | - [Container view](container-view) 11 | - [Component view](component-view) 12 | - [Dynamic view](dynamic-view) 13 | - [Deployment view](deployment-view) 14 | - [Amazon Web Services](amazon-web-services) 15 | - [Element styles](element-styles) 16 | - [Relationship styles](relationship-styles) 17 | - [Themes](themes) 18 | - [Implied relationships](implied-relationships) -------------------------------------------------------------------------------- /docs/cookbook/deployment-view/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" "" "Spring Boot" 7 | database = container "Database" "" "Relational database schema" 8 | } 9 | 10 | u -> webapp "Uses" 11 | webapp -> database "Reads from and writes to" 12 | 13 | development = deploymentEnvironment "Development" { 14 | deploymentNode "Developer Laptop" { 15 | containerInstance webapp 16 | deploymentNode "MySQL" { 17 | containerInstance database 18 | } 19 | } 20 | } 21 | } 22 | 23 | views { 24 | deployment * development { 25 | include * 26 | autoLayout lr 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ContainerDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Container; 4 | import com.structurizr.model.GroupableElement; 5 | import com.structurizr.model.ModelItem; 6 | 7 | final class ContainerDslContext extends GroupableElementDslContext { 8 | 9 | private Container container; 10 | 11 | ContainerDslContext(Container container) { 12 | this(container, null); 13 | } 14 | 15 | ContainerDslContext(Container container, ElementGroup group) { 16 | super(group); 17 | 18 | this.container = container; 19 | } 20 | 21 | Container getContainer() { 22 | return container; 23 | } 24 | 25 | @Override 26 | ModelItem getModelItem() { 27 | return getContainer(); 28 | } 29 | 30 | @Override 31 | GroupableElement getElement() { 32 | return container; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /docs/cookbook/workspace/README.md: -------------------------------------------------------------------------------- 1 | # Workspace 2 | 3 | In Structurizr terminology, a "workspace" is a wrapper for a software architecture model (elements and relationships) and views. 4 | 5 | ``` 6 | workspace "Name" "Description" { 7 | 8 | model { 9 | } 10 | 11 | views { 12 | } 13 | 14 | } 15 | ``` 16 | 17 | A workspace can be given a name and description, although these are only used by the [Structurizr cloud service and on-premises installation](https://structurizr.com) - you don't need to specify a name/description if you're exporting views to one of the export formats (PlantUML, Mermaid, etc). 18 | 19 | ## Links 20 | 21 | - [DSL language reference - workspace](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#workspace) 22 | - [Example](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/workspace/example-1.dsl) 23 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ContainerInstanceDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.ContainerInstance; 4 | import com.structurizr.model.ModelItem; 5 | import com.structurizr.model.StaticStructureElementInstance; 6 | 7 | final class ContainerInstanceDslContext extends StaticStructureElementInstanceDslContext { 8 | 9 | private ContainerInstance containerInstance; 10 | 11 | ContainerInstanceDslContext(ContainerInstance containerInstance) { 12 | this.containerInstance = containerInstance; 13 | } 14 | 15 | ContainerInstance getContainerInstance() { 16 | return containerInstance; 17 | } 18 | 19 | @Override 20 | ModelItem getModelItem() { 21 | return getContainerInstance(); 22 | } 23 | 24 | @Override 25 | StaticStructureElementInstance getElementInstance() { 26 | return getContainerInstance(); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /examples/extend/extend-workspace-from-dsl-url.dsl: -------------------------------------------------------------------------------- 1 | workspace extends https://raw.githubusercontent.com/structurizr/dsl/master/examples/extend/workspace.dsl { 2 | name "A new name" 3 | description "A new description" 4 | 5 | model { 6 | softwareSystem = softwareSystem "Software System" { 7 | webapp = container "Web Application" 8 | } 9 | } 10 | 11 | views { 12 | systemContext softwareSystem "SystemContext" "An example of a System Context diagram." { 13 | include * 14 | autoLayout 15 | } 16 | 17 | styles { 18 | element "Software System" { 19 | background #1168bd 20 | color #ffffff 21 | } 22 | element "Person" { 23 | shape person 24 | background #08427b 25 | color #ffffff 26 | } 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /examples/extend/extend-workspace-from-json-url.dsl: -------------------------------------------------------------------------------- 1 | workspace extends https://raw.githubusercontent.com/structurizr/dsl/master/examples/extend/workspace.json { 2 | name "A new name" 3 | description "A new description" 4 | 5 | model { 6 | softwareSystem = softwareSystem "Software System" { 7 | webapp = container "Web Application" 8 | } 9 | } 10 | 11 | views { 12 | systemContext softwareSystem "SystemContext" "An example of a System Context diagram." { 13 | include * 14 | autoLayout 15 | } 16 | 17 | styles { 18 | element "Software System" { 19 | background #1168bd 20 | color #ffffff 21 | } 22 | element "Person" { 23 | shape person 24 | background #08427b 25 | color #ffffff 26 | } 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StructurizrDslExpressions.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | class StructurizrDslExpressions { 4 | 5 | static final String ELEMENT_TYPE_EQUALS_EXPRESSION = "element.type=="; 6 | static final String ELEMENT_TAG_EQUALS_EXPRESSION = "element.tag=="; 7 | static final String ELEMENT_TAG_NOT_EQUALS_EXPRESSION = "element.tag!="; 8 | 9 | static final String ELEMENT_EQUALS_EXPRESSION = "element=="; 10 | 11 | static final String RELATIONSHIP_TAG_EQUALS_EXPRESSION = "relationship.tag=="; 12 | static final String RELATIONSHIP_TAG_NOT_EQUALS_EXPRESSION = "relationship.tag!="; 13 | 14 | static final String RELATIONSHIP_SOURCE_EQUALS_EXPRESSION = "relationship.source=="; 15 | static final String RELATIONSHIP_DESTINATION_EQUALS_EXPRESSION = "relationship.destination=="; 16 | 17 | static final String RELATIONSHIP_EQUALS_EXPRESSION = "relationship=="; 18 | 19 | static final String RELATIONSHIP = "->"; 20 | 21 | } -------------------------------------------------------------------------------- /examples/hierarchical-identifiers-and-deployment-nodes.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | !identifiers hierarchical 4 | 5 | model { 6 | ss = softwareSystem "Software System" 7 | 8 | deploymentEnvironment "Live" { 9 | 10 | dn = deploymentNode "DN" { 11 | dn1 = deploymentNode "DN1" { 12 | softwareSystemInstance ss 13 | } 14 | 15 | dn2 = deploymentNode "DN2" { 16 | softwareSystemInstance ss 17 | } 18 | 19 | dn1 -> dn2 20 | } 21 | 22 | dn1 = deploymentNode "DN1" { 23 | softwareSystemInstance ss 24 | } 25 | 26 | dn2 = deploymentNode "DN2" { 27 | softwareSystemInstance ss 28 | } 29 | 30 | dn1 -> dn2 31 | } 32 | } 33 | 34 | views { 35 | deployment * "Live" { 36 | include * 37 | } 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SoftwareSystemInstanceDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.SoftwareSystemInstance; 4 | import com.structurizr.model.ModelItem; 5 | import com.structurizr.model.StaticStructureElementInstance; 6 | 7 | final class SoftwareSystemInstanceDslContext extends StaticStructureElementInstanceDslContext { 8 | 9 | private SoftwareSystemInstance softwareSystemInstance; 10 | 11 | SoftwareSystemInstanceDslContext(SoftwareSystemInstance softwareSystemInstance) { 12 | this.softwareSystemInstance = softwareSystemInstance; 13 | } 14 | 15 | SoftwareSystemInstance getSoftwareSystemInstance() { 16 | return softwareSystemInstance; 17 | } 18 | 19 | @Override 20 | ModelItem getModelItem() { 21 | return getSoftwareSystemInstance(); 22 | } 23 | 24 | @Override 25 | StaticStructureElementInstance getElementInstance() { 26 | return getSoftwareSystemInstance(); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SoftwareSystemDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.GroupableElement; 4 | import com.structurizr.model.ModelItem; 5 | import com.structurizr.model.SoftwareSystem; 6 | 7 | final class SoftwareSystemDslContext extends GroupableElementDslContext { 8 | 9 | private SoftwareSystem softwareSystem; 10 | 11 | SoftwareSystemDslContext(SoftwareSystem softwareSystem) { 12 | this(softwareSystem, null); 13 | } 14 | 15 | SoftwareSystemDslContext(SoftwareSystem softwareSystem, ElementGroup group) { 16 | super(group); 17 | 18 | this.softwareSystem = softwareSystem; 19 | } 20 | 21 | SoftwareSystem getSoftwareSystem() { 22 | return softwareSystem; 23 | } 24 | 25 | @Override 26 | ModelItem getModelItem() { 27 | return getSoftwareSystem(); 28 | } 29 | 30 | @Override 31 | GroupableElement getElement() { 32 | return softwareSystem; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DslParserContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import java.io.File; 4 | 5 | final class DslParserContext extends DslContext { 6 | 7 | private boolean restricted; 8 | private File file; 9 | 10 | DslParserContext(File file, boolean restricted) { 11 | this.file = file; 12 | this.restricted = restricted; 13 | } 14 | 15 | File getFile() { 16 | return file; 17 | } 18 | 19 | boolean isRestricted() { 20 | return restricted; 21 | } 22 | 23 | void copyFrom(IdentifersRegister identifersRegister) { 24 | for (String identifier : identifersRegister.getElementIdentifiers()) { 25 | this.identifersRegister.register(identifier, identifersRegister.getElement(identifier)); 26 | } 27 | 28 | for (String identifier : identifersRegister.getRelationshipIdentifiers()) { 29 | this.identifersRegister.register(identifier, identifersRegister.getRelationship(identifier)); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /examples/extend/workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : 0, 3 | "name" : "Getting Started", 4 | "description" : "This is a model of my software system.", 5 | "configuration" : { }, 6 | "model" : { 7 | "people" : [ { 8 | "id" : "1", 9 | "tags" : "Element,Person", 10 | "name" : "User", 11 | "description" : "A user of my software system.", 12 | "relationships" : [ { 13 | "id" : "3", 14 | "tags" : "Relationship", 15 | "sourceId" : "1", 16 | "destinationId" : "2", 17 | "description" : "Uses" 18 | } ], 19 | "location" : "Unspecified" 20 | } ], 21 | "softwareSystems" : [ { 22 | "id" : "2", 23 | "tags" : "Element,Software System", 24 | "name" : "Software System", 25 | "description" : "My software system.", 26 | "location" : "Unspecified" 27 | } ] 28 | }, 29 | "documentation" : { }, 30 | "views" : { 31 | "configuration" : { 32 | "branding" : { }, 33 | "styles" : { }, 34 | "terminology" : { } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/RefParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Element; 4 | 5 | final class RefParser extends AbstractParser { 6 | 7 | private static final String GRAMMAR = "ref "; 8 | 9 | private final static int NAME_INDEX = 1; 10 | 11 | Element parse(DslContext context, Tokens tokens) { 12 | // ref 13 | 14 | if (tokens.hasMoreThan(NAME_INDEX)) { 15 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 16 | } 17 | 18 | if (!tokens.includes(NAME_INDEX)) { 19 | throw new RuntimeException("Expected: " + GRAMMAR); 20 | } 21 | 22 | String canonicalName = tokens.get(NAME_INDEX); 23 | Element element = context.getWorkspace().getModel().getElementWithCanonicalName(canonicalName); 24 | 25 | if (element == null) { 26 | throw new RuntimeException(canonicalName + " could not be found"); 27 | } 28 | 29 | return element; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/Tokens.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import java.util.List; 4 | 5 | final class Tokens { 6 | 7 | private List tokens; 8 | 9 | Tokens(List tokens) { 10 | this.tokens = tokens; 11 | } 12 | 13 | String get(int index) { 14 | return tokens.get(index).trim().replaceAll("\\\\\"", "\"").trim(); 15 | } 16 | 17 | int size() { 18 | return tokens.size(); 19 | } 20 | 21 | boolean contains(String token) { 22 | return tokens.contains(token.trim()); 23 | } 24 | 25 | Tokens withoutContextStartToken() { 26 | if (tokens.get(tokens.size()-1).equals(DslContext.CONTEXT_START_TOKEN)) { 27 | return new Tokens(tokens.subList(0, tokens.size()-1)); 28 | } else { 29 | return this; 30 | } 31 | } 32 | 33 | boolean includes(int index) { 34 | return tokens.size() - 1 >= index; 35 | } 36 | 37 | boolean hasMoreThan(int index) { 38 | return includes(index + 1); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/AbstractTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.model.CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy; 5 | import com.structurizr.model.Model; 6 | import com.structurizr.view.ViewSet; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | 11 | abstract class AbstractTests { 12 | 13 | protected Workspace workspace = new Workspace("Name", "Description"); 14 | protected Model model = workspace.getModel(); 15 | protected ViewSet views = workspace.getViews(); 16 | 17 | protected ModelDslContext context() { 18 | model.setImpliedRelationshipsStrategy(new CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy()); 19 | 20 | ModelDslContext context = new ModelDslContext(); 21 | context.setWorkspace(workspace); 22 | 23 | return context; 24 | } 25 | 26 | protected Tokens tokens(String... tokens) { 27 | return new Tokens(new ArrayList<>(Arrays.asList(tokens))); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/IdentifierScopeParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class IdentifierScopeParser extends AbstractParser { 4 | 5 | private static final String GRAMMAR = "!identifiers "; 6 | 7 | private static final int MODE_INDEX = 1; 8 | 9 | IdentifierScope parse(DslContext context, Tokens tokens) { 10 | // !identifiers 11 | 12 | if (tokens.hasMoreThan(MODE_INDEX)) { 13 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 14 | } 15 | 16 | if (!tokens.includes(MODE_INDEX)) { 17 | throw new RuntimeException("Expected: " + GRAMMAR); 18 | } 19 | 20 | String name = tokens.get(MODE_INDEX); 21 | if ("flat".equalsIgnoreCase(name)) { 22 | return IdentifierScope.Flat; 23 | } else if ("hierarchical".equalsIgnoreCase(name)) { 24 | return IdentifierScope.Hierarchical; 25 | } else { 26 | throw new RuntimeException("Expected: " + GRAMMAR); 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DslUtils.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.util.StringUtils; 5 | 6 | import java.nio.charset.StandardCharsets; 7 | import java.util.Base64; 8 | 9 | public class DslUtils { 10 | 11 | private static final String STRUCTURIZR_DSL_PROPERTY_NAME = "structurizr.dsl"; 12 | 13 | public static String getDsl(Workspace workspace) { 14 | String base64 = workspace.getProperties().get(STRUCTURIZR_DSL_PROPERTY_NAME); 15 | String dsl = ""; 16 | 17 | if (!StringUtils.isNullOrEmpty(base64)) { 18 | dsl = new String(Base64.getDecoder().decode(base64)); 19 | } 20 | 21 | return dsl; 22 | } 23 | 24 | static void setDsl(Workspace workspace, String dsl) { 25 | String base64 = ""; 26 | if (!StringUtils.isNullOrEmpty(dsl)) { 27 | base64 = Base64.getEncoder().encodeToString(dsl.getBytes(StandardCharsets.UTF_8)); 28 | } 29 | 30 | workspace.addProperty(STRUCTURIZR_DSL_PROPERTY_NAME, base64); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/IncludeParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class IncludeParserTests extends AbstractTests { 9 | 10 | private IncludeParser parser = new IncludeParser(); 11 | 12 | @Test 13 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 14 | try { 15 | parser.parse(new IncludedDslContext(null), tokens("!include", "file", "extra")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Too many tokens, expected: !include ", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parse_ThrowsAnException_WhenAFileIsNotSpefieid() { 24 | try { 25 | parser.parse(new IncludedDslContext(null), tokens("!include")); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: !include ", e.getMessage()); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /docs/cookbook/dynamic-view/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | customer = person "Customer" 5 | onlineBookStore = softwareSystem "Online book store" { 6 | webapp = container "Web Application" 7 | database = container "Database" 8 | } 9 | 10 | customer -> webapp "Browses and makes purchases using" 11 | webapp -> database "Reads from and writes to" 12 | } 13 | 14 | views { 15 | container onlineBookStore { 16 | include * 17 | autoLayout lr 18 | } 19 | 20 | dynamic onlineBookStore { 21 | title "Request past orders feature" 22 | customer -> webapp "Requests past orders from" 23 | webapp -> database "Queries for orders using" 24 | autoLayout lr 25 | } 26 | 27 | dynamic onlineBookStore { 28 | title "Browse top 20 books feature" 29 | customer -> webapp "Requests the top 20 books from" 30 | webapp -> database "Queries the top 20 books using" 31 | autoLayout lr 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ElementGroup.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Element; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | class ElementGroup extends Element { 9 | 10 | private Element parent; 11 | private String name; 12 | 13 | private Set elements = new HashSet<>(); 14 | 15 | ElementGroup(String name) { 16 | this.name = name; 17 | } 18 | 19 | @Override 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | @Override 25 | public String getCanonicalName() { 26 | return name; 27 | } 28 | 29 | void setParent(Element parent) { 30 | this.parent = parent; 31 | } 32 | 33 | @Override 34 | public Element getParent() { 35 | return parent; 36 | } 37 | 38 | @Override 39 | protected Set getRequiredTags() { 40 | return null; 41 | } 42 | 43 | void addElement(Element element) { 44 | elements.add(element); 45 | } 46 | 47 | Set getElements() { 48 | return new HashSet<>(elements); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ConstantParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class ConstantParser extends AbstractParser { 4 | 5 | private static final String GRAMMAR = "!constant "; 6 | 7 | private static final int NAME_INDEX = 1; 8 | private static final int VALUE_INDEX = 2; 9 | 10 | private static final String NAME_REGEX = "[a-zA-Z0-9-_.]+"; 11 | 12 | Constant parse(DslContext context, Tokens tokens) { 13 | // !constant name value 14 | 15 | if (tokens.hasMoreThan(VALUE_INDEX)) { 16 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 17 | } 18 | 19 | if (!tokens.includes(VALUE_INDEX)) { 20 | throw new RuntimeException("Expected: " + GRAMMAR); 21 | } 22 | 23 | String name = tokens.get(NAME_INDEX); 24 | String value = tokens.get(VALUE_INDEX); 25 | 26 | if (!name.matches(NAME_REGEX)) { 27 | throw new RuntimeException("Constant names must only contain the following characters: a-zA-Z0-9-_."); 28 | } 29 | 30 | return new Constant(name, value); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Element; 4 | 5 | import java.util.Objects; 6 | import java.util.Set; 7 | 8 | class DeploymentEnvironment extends Element { 9 | 10 | private String name; 11 | 12 | DeploymentEnvironment(String name) { 13 | this.name = name; 14 | } 15 | 16 | @Override 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | @Override 22 | public String getCanonicalName() { 23 | return name; 24 | } 25 | 26 | @Override 27 | public Element getParent() { 28 | return null; 29 | } 30 | 31 | @Override 32 | protected Set getRequiredTags() { 33 | return null; 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) { 38 | if (this == o) return true; 39 | if (o == null || getClass() != o.getClass()) return false; 40 | DeploymentEnvironment that = (DeploymentEnvironment) o; 41 | return name.equals(that.name); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(name); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/CustomViewAnimationStepParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class CustomViewAnimationStepParserTests extends AbstractTests { 9 | 10 | private CustomViewAnimationStepParser parser = new CustomViewAnimationStepParser(); 11 | 12 | @Test 13 | void test_parseExplicit_ThrowsAnException_WhenElementsAreMissing() { 14 | try { 15 | parser.parse((CustomViewDslContext)null, tokens("animationStep")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Expected: animationStep [identifier...]", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parseImplicit_ThrowsAnException_WhenElementsAreMissing() { 24 | try { 25 | parser.parse((CustomViewAnimationDslContext) null, tokens()); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: [identifier...]", e.getMessage()); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/StaticViewAnimationStepParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class StaticViewAnimationStepParserTests extends AbstractTests { 9 | 10 | private StaticViewAnimationStepParser parser = new StaticViewAnimationStepParser(); 11 | 12 | @Test 13 | void test_parseExplicit_ThrowsAnException_WhenElementsAreMissing() { 14 | try { 15 | parser.parse((StaticViewDslContext)null, tokens("animationStep")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Expected: animationStep [identifier...]", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parseImplicit_ThrowsAnException_WhenElementsAreMissing() { 24 | try { 25 | parser.parse((StaticViewAnimationDslContext) null, tokens()); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: [identifier...]", e.getMessage()); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/GroupParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class GroupParserTests extends AbstractTests { 8 | 9 | private GroupParser parser = new GroupParser(); 10 | 11 | @Test 12 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 13 | try { 14 | parser.parse(tokens("group", "name", "extra")); 15 | fail(); 16 | } catch (Exception e) { 17 | assertEquals("Too many tokens, expected: group {", e.getMessage()); 18 | } 19 | } 20 | 21 | @Test 22 | void test_parse_ThrowsAnException_WhenTheNameIsMissing() { 23 | try { 24 | parser.parse(tokens("group")); 25 | fail(); 26 | } catch (Exception e) { 27 | assertEquals("Expected: group {", e.getMessage()); 28 | } 29 | } 30 | 31 | @Test 32 | void test_parse() { 33 | ElementGroup group = parser.parse(tokens("group", "Group 1")); 34 | assertEquals("Group 1", group.getName()); 35 | assertTrue(group.getElements().isEmpty()); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/DeploymentViewAnimationStepParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class DeploymentViewAnimationStepParserTests extends AbstractTests { 9 | 10 | private DeploymentViewAnimationStepParser parser = new DeploymentViewAnimationStepParser(); 11 | 12 | @Test 13 | void test_parseExplicit_ThrowsAnException_WhenElementsAreMissing() { 14 | try { 15 | parser.parse((DeploymentViewDslContext)null, tokens("animationStep")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Expected: animationStep [identifier...]", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parseImplicit_ThrowsAnException_WhenElementsAreMissing() { 24 | try { 25 | parser.parse((DeploymentViewAnimationDslContext)null, tokens()); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: [identifier...]", e.getMessage()); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ViewParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.View; 4 | 5 | final class ViewParser extends AbstractParser { 6 | 7 | private static final String GRAMMAR = "title "; 8 | 9 | private static final int TITLE_INDEX = 1; 10 | 11 | void parseTitle(StaticViewDslContext context, Tokens tokens) { 12 | parseTitle(context.getView(), tokens); 13 | } 14 | 15 | void parseTitle(DynamicViewDslContext context, Tokens tokens) { 16 | parseTitle(context.getView(), tokens); 17 | } 18 | 19 | void parseTitle(DeploymentViewDslContext context, Tokens tokens) { 20 | parseTitle(context.getView(), tokens); 21 | } 22 | 23 | private void parseTitle(View view, Tokens tokens) { 24 | // title <title> 25 | 26 | if (tokens.hasMoreThan(TITLE_INDEX)) { 27 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 28 | } 29 | 30 | if (view != null) { 31 | if (tokens.size() == 2) { 32 | String title = tokens.get(TITLE_INDEX); 33 | 34 | view.setTitle(title); 35 | } else { 36 | throw new RuntimeException("Expected: " + GRAMMAR); 37 | } 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/DeploymentGroupParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class DeploymentGroupParserTests extends AbstractTests { 9 | 10 | private DeploymentGroupParser parser = new DeploymentGroupParser(); 11 | 12 | @Test 13 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 14 | try { 15 | parser.parse(tokens("deploymentGroup", "name", "extra")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Too many tokens, expected: deploymentGroup <name>", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parse_ThrowsAnException_WhenTheNameIsMissing() { 24 | try { 25 | parser.parse(tokens("deploymentGroup")); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: deploymentGroup <name>", e.getMessage()); 29 | } 30 | } 31 | 32 | @Test 33 | void test_parse() { 34 | String service1 = parser.parse(tokens("deploymentGroup", "Service 1")); 35 | assertEquals("Service 1", service1); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/EnterpriseParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class EnterpriseParserTests extends AbstractTests { 8 | 9 | private EnterpriseParser parser = new EnterpriseParser(); 10 | 11 | @Test 12 | void test_parse_SetsTheEnterpriseName() { 13 | assertNull(workspace.getModel().getEnterprise()); 14 | parser.parse(context(), tokens("enterprise", "New Name")); 15 | assertEquals("New Name", workspace.getModel().getEnterprise().getName()); 16 | } 17 | 18 | @Test 19 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 20 | try { 21 | parser.parse(context(), tokens("enterprise", "name", "extra")); 22 | fail(); 23 | } catch (Exception e) { 24 | assertEquals("Too many tokens, expected: enterprise <name>", e.getMessage()); 25 | } 26 | } 27 | 28 | @Test 29 | void test_parse_ThrowsAnException_WhenNoNameIsSpecified() { 30 | try { 31 | parser.parse(context(), tokens("enterprise")); 32 | fail(); 33 | } catch (Exception e) { 34 | assertEquals("Expected: enterprise <name>", e.getMessage()); 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/UserRoleParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.configuration.Role; 4 | 5 | final class UserRoleParser extends AbstractParser { 6 | 7 | private static final String GRAMMAR = "<username> <read|write>"; 8 | 9 | private final static int USERNAME_INDEX = 0; 10 | private final static int ROLE_INDEX = 1; 11 | 12 | void parse(DslContext context, Tokens tokens) { 13 | // <username> <read|write> 14 | 15 | if (tokens.hasMoreThan(ROLE_INDEX)) { 16 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 17 | } 18 | 19 | if (tokens.size() != 2) { 20 | throw new RuntimeException("Expected: " + GRAMMAR); 21 | } 22 | 23 | String username = tokens.get(USERNAME_INDEX); 24 | String roleAsString = tokens.get(ROLE_INDEX); 25 | 26 | Role role; 27 | 28 | if (roleAsString.equalsIgnoreCase("write")) { 29 | role = Role.ReadWrite; 30 | } else if (roleAsString.equalsIgnoreCase("read")) { 31 | role = Role.ReadOnly; 32 | } else { 33 | throw new RuntimeException("The role should be \"read\" or \"write\""); 34 | } 35 | 36 | context.getWorkspace().getConfiguration().addUser(username, role); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/DeploymentEnvironmentParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class DeploymentEnvironmentParserTests extends AbstractTests { 9 | 10 | private DeploymentEnvironmentParser parser = new DeploymentEnvironmentParser(); 11 | 12 | @Test 13 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 14 | try { 15 | parser.parse(tokens("deploymentEnvironment", "name", "extra")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Too many tokens, expected: deploymentEnvironment <name> {", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parse_ThrowsAnException_WhenTheNameIsMissing() { 24 | try { 25 | parser.parse(tokens("deploymentEnvironment")); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: deploymentEnvironment <name> {", e.getMessage()); 29 | } 30 | } 31 | 32 | @Test 33 | void test_parse() { 34 | String environment = parser.parse(tokens("deploymentEnvironment", "Live")); 35 | assertEquals("Live", environment); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.13.0 (unreleased) 4 | 5 | - __Breaking change__: `impliedRelationships` is now `!impliedRelationships`. 6 | - Adds support for using custom implied relationship strategies. 7 | - Adds support for "include relationship==*" (#68). 8 | - Fixes #69 (hierarchical identifiers not working at the top level inside a deployment environment). 9 | 10 | ## 1.12.0 (30th June 2021) 11 | 12 | - Adds an `!identifiers` keyword to specify whether element identifiers should be `flat` (default) or `hierarchical`. 13 | - Adds support for a `this` identifier when defining relationships inside element definitions. 14 | - Fixes links between ADRs. 15 | 16 | ## 1.11.0 (7th June 2021) 17 | 18 | - __Breaking change__: Default styles are no longer added; use the `default` theme to add some default styles instead. 19 | - __Breaking change__: Relationship expressions (e.g. * -> *) now need to be surrounded in double quotes. 20 | - Support parallel activities in dynamic view (issue #53). 21 | - Adds a `tags` keyword for adding tags to elements/relationships. 22 | - Adds a `theme` keyword for adding a single theme. 23 | - Adds support to `!include` from a HTTPS URL. 24 | - Adds support for referencing groups by identifier. 25 | - Adds support for extending a workspace. 26 | 27 | ## 1.10.0 (27th April 2021) 28 | 29 | - First version released onto Maven Central. -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ModelDslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Location; 4 | 5 | final class ModelDslContext extends GroupableDslContext { 6 | 7 | ModelDslContext() { 8 | super(null); 9 | } 10 | 11 | ModelDslContext(ElementGroup group) { 12 | super(group); 13 | } 14 | 15 | @Override 16 | void end() { 17 | // the location is only set to internal when created inside an "enterprise" block 18 | // - if some elements have been marked as internal, let's assume the rest are external 19 | boolean modelHasInternalPeople = getWorkspace().getModel().getPeople().stream().anyMatch(p -> p.getLocation() == Location.Internal); 20 | if (modelHasInternalPeople) { 21 | getWorkspace().getModel().getPeople().stream().filter(p -> p.getLocation() != Location.Internal).forEach(p -> p.setLocation(Location.External)); 22 | } 23 | 24 | boolean modelHasInternalSoftwareSystems = getWorkspace().getModel().getSoftwareSystems().stream().anyMatch(ss -> ss.getLocation() == Location.Internal); 25 | if (modelHasInternalSoftwareSystems) { 26 | getWorkspace().getModel().getSoftwareSystems().stream().filter(ss -> ss.getLocation() != Location.Internal).forEach(ss -> ss.setLocation(Location.External)); 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /examples/filteredviews.dsl: -------------------------------------------------------------------------------- 1 | workspace "FilteredDemo" "This is an example of Filtered views" { 2 | 3 | # This will render the two diagrams at https://structurizr.com/help/filtered-views 4 | 5 | model { 6 | 7 | user = person "Customer" "A description of the user." 8 | sysa = softwareSystem "Software System A" "A description of software system A." 9 | 10 | user -> sysa "Uses for tasks 1 and 2" "" Current 11 | 12 | sysb = softwareSystem "Software System B" "A description of software system B." Future 13 | 14 | user -> sysa "Uses for task 1" "" Future 15 | user -> sysb "Uses for task 2" "" Future 16 | 17 | } 18 | 19 | views { 20 | 21 | systemLandscape FullLandscape "System Landscape, current and future" { 22 | include * 23 | } 24 | 25 | filtered FullLandscape exclude Future CurrentLandscape "The current system landscape." 26 | filtered FullLandscape exclude Current FutureLandscape "The future state system landscape after Software System B is live." 27 | 28 | styles { 29 | element "Software System" { 30 | background #91a437 31 | shape RoundedBox 32 | } 33 | 34 | element "Person" { 35 | background #6a7b15 36 | shape Person 37 | } 38 | } 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | final class FileUtils { 9 | 10 | private static final String STRUCTURIZR_DSL_FILE_EXTENSION = ".dsl"; 11 | 12 | static List<File> findFiles(File path) { 13 | List<File> files = new ArrayList<>(); 14 | if (path.isDirectory()) { 15 | files = findFilesInDirectory(path); 16 | } else { 17 | files.add(path); 18 | } 19 | 20 | return files; 21 | } 22 | 23 | private static List<File> findFilesInDirectory(File directory) { 24 | List<File> files = new ArrayList<>(); 25 | 26 | File[] filesInDirectory = directory.listFiles(); 27 | if (filesInDirectory == null || filesInDirectory.length == 0) { 28 | return files; 29 | } 30 | 31 | Arrays.sort(filesInDirectory); 32 | 33 | for (File file : filesInDirectory) { 34 | if (!file.isDirectory() && file.getName().endsWith(STRUCTURIZR_DSL_FILE_EXTENSION)) { 35 | files.add(file); 36 | } 37 | 38 | if (file.isDirectory()) { 39 | files.addAll(findFilesInDirectory(file)); 40 | } 41 | } 42 | 43 | return files; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /docs/cookbook/system-context-view/README.md: -------------------------------------------------------------------------------- 1 | # System Context view 2 | 3 | A [system context view](https://c4model.com/#SystemContextDiagram) is a good starting point for diagramming and documenting a software system, allowing you to step back and see the big picture. It shows the software system in the centre, surrounded by its users and the other systems that it interacts with. 4 | 5 | ``` 6 | workspace { 7 | 8 | model { 9 | u = person "User" 10 | s = softwareSystem "Software System" 11 | 12 | u -> s "Uses" 13 | } 14 | 15 | views { 16 | systemContext s { 17 | include * 18 | autoLayout lr 19 | } 20 | } 21 | 22 | } 23 | ``` 24 | 25 | This DSL defines a system context view for the software system `s`, and `include *` includes all model elements that have a direct relationship with it. 26 | 27 | [![](example-1.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/system-context-view/example-1.dsl) 28 | 29 | System context views can be rendered using the Structurizr cloud service/on-premises installation or exported to a number of other formats via the [Structurizr CLI export command](https://github.com/structurizr/cli/blob/master/docs/export.md). 30 | 31 | ## Links 32 | 33 | - [DSL language reference - systemContext](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#systemContext-view) -------------------------------------------------------------------------------- /examples/groups.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | softwareSystem = softwareSystem "Software System" { 5 | service1 = group "Service 1" { 6 | service1Api = container "Service 1 API" 7 | service1Database = container "Service 1 Database" 8 | 9 | service1Api -> service1Database "Reads from and writes to" 10 | } 11 | service2 = group "Service 2" { 12 | service2Api = container "Service 2 API" 13 | service2Database = container "Service 2 Database" 14 | 15 | service2Api -> service2Database "Reads from and writes to" 16 | } 17 | } 18 | 19 | live = deploymentEnvironment "Live" { 20 | deploymentNode "Server 1" { 21 | containerInstance service1Api 22 | containerInstance service1Database 23 | } 24 | deploymentNode "Server 2" { 25 | containerInstance service2Api 26 | containerInstance service2Database 27 | } 28 | } 29 | 30 | service1Api -> service2Api "Uses" 31 | } 32 | 33 | views { 34 | container softwareSystem { 35 | include service1 service2 36 | autolayout 37 | } 38 | 39 | deployment softwareSystem live { 40 | include service1 service2 41 | autolayout 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ThemeParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class ThemeParser extends AbstractParser { 4 | 5 | private static final String DEFAULT_THEME_URL = "https://static.structurizr.com/themes/default/theme.json"; 6 | 7 | private final static int FIRST_THEME_INDEX = 1; 8 | 9 | void parseTheme(DslContext context, Tokens tokens) { 10 | // theme <default|url> 11 | if (tokens.hasMoreThan(FIRST_THEME_INDEX)) { 12 | throw new RuntimeException("Too many tokens, expected: theme <url>"); 13 | } 14 | 15 | if (!tokens.includes(FIRST_THEME_INDEX)) { 16 | throw new RuntimeException("Expected: theme <url>"); 17 | } 18 | 19 | addTheme(context, tokens.get(FIRST_THEME_INDEX)); 20 | } 21 | 22 | void parseThemes(DslContext context, Tokens tokens) { 23 | // themes <url> [url] ... [url] 24 | if (!tokens.includes(FIRST_THEME_INDEX)) { 25 | throw new RuntimeException("Expected: themes <url> [url] ... [url]"); 26 | } 27 | 28 | for (int i = FIRST_THEME_INDEX; i < tokens.size(); i++) { 29 | addTheme(context, tokens.get(i)); 30 | } 31 | } 32 | 33 | private void addTheme(DslContext context, String url) { 34 | if ("default".equalsIgnoreCase(url)) { 35 | url = DEFAULT_THEME_URL; 36 | } 37 | 38 | context.getWorkspace().getViews().getConfiguration().addTheme(url); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SystemLandscapeViewParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.view.SystemLandscapeView; 5 | 6 | final class SystemLandscapeViewParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "systemLandscape [key] [description] {"; 9 | 10 | private static final String VIEW_TYPE = "SystemLandscape"; 11 | 12 | private static final int KEY_INDEX = 1; 13 | private static final int DESCRIPTION_INDEX = 2; 14 | 15 | SystemLandscapeView parse(DslContext context, Tokens tokens) { 16 | // systemLandscape [key] [description] 17 | 18 | if (tokens.hasMoreThan(DESCRIPTION_INDEX)) { 19 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 20 | } 21 | 22 | Workspace workspace = context.getWorkspace(); 23 | String key = ""; 24 | String description = ""; 25 | 26 | if (tokens.includes(KEY_INDEX)) { 27 | key = tokens.get(KEY_INDEX); 28 | } else { 29 | key = VIEW_TYPE; 30 | } 31 | validateViewKey(key); 32 | 33 | if (tokens.includes(DESCRIPTION_INDEX)) { 34 | description = tokens.get(DESCRIPTION_INDEX); 35 | } 36 | 37 | SystemLandscapeView view = workspace.getViews().createSystemLandscapeView(key, description); 38 | view.setEnterpriseBoundaryVisible(true); 39 | 40 | return view; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /docs/cookbook/container-view/README.md: -------------------------------------------------------------------------------- 1 | # Container view 2 | 3 | A [container view](https://c4model.com/#ContainerDiagram) allows you to zoom-in to the software system shown on a system context view, to see the applications and data stores (what the C4 model refers to as "containers") that reside inside it. 4 | 5 | ``` 6 | workspace { 7 | 8 | model { 9 | u = person "User" 10 | s = softwareSystem "Software System" { 11 | webapp = container "Web Application" 12 | database = container "Database" 13 | } 14 | 15 | u -> webapp "Uses" 16 | webapp -> database "Reads from and writes to" 17 | } 18 | 19 | views { 20 | container s { 21 | include * 22 | autoLayout lr 23 | } 24 | } 25 | 26 | } 27 | ``` 28 | 29 | This DSL defines a container view for the software system `s`, and `include *` includes the default set of model elements for the view. 30 | 31 | [![](example-1.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/container-view/example-1.dsl) 32 | 33 | Container views can be rendered using the Structurizr cloud service/on-premises installation or exported to a number of other formats via the [Structurizr CLI export command](https://github.com/structurizr/cli/blob/master/docs/export.md). 34 | 35 | ## Links 36 | 37 | - [DSL language reference - container](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#container-view) -------------------------------------------------------------------------------- /examples/deployment-groups.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | softwareSystem = softwareSystem "Software System" { 5 | database = container "Database" 6 | api = container "Service API" { 7 | -> database "Uses" 8 | } 9 | } 10 | 11 | deploymentEnvironment "Example 1" { 12 | deploymentNode "Server 1" { 13 | containerInstance api 14 | containerInstance database 15 | } 16 | deploymentNode "Server 2" { 17 | containerInstance api 18 | containerInstance database 19 | } 20 | } 21 | 22 | deploymentEnvironment "Example 2" { 23 | serviceInstance1 = deploymentGroup "Service Instance 1" 24 | serviceInstance2 = deploymentGroup "Service Instance 2" 25 | deploymentNode "Server 1" { 26 | containerInstance api serviceInstance1 27 | containerInstance database serviceInstance1 28 | } 29 | deploymentNode "Server 2" { 30 | containerInstance api serviceInstance2 31 | containerInstance database serviceInstance2 32 | } 33 | } 34 | } 35 | 36 | views { 37 | deployment * "Example 1" { 38 | include * 39 | autolayout 40 | } 41 | 42 | deployment * "Example 2" { 43 | include * 44 | autolayout 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /docs/cookbook/component-view/README.md: -------------------------------------------------------------------------------- 1 | # Component view 2 | 3 | A [component view](https://c4model.com/#ComponentDiagram) allows you to zoom-in to a container, to see the components that reside inside it. 4 | 5 | ``` 6 | workspace { 7 | 8 | model { 9 | u = person "User" 10 | s = softwareSystem "Software System" { 11 | webapp = container "Web Application" { 12 | c1 = component "Component 1" 13 | c2 = component "Component 2" 14 | } 15 | database = container "Database" 16 | } 17 | 18 | u -> c1 "Uses" 19 | c1 -> c2 "Uses" 20 | c2 -> database "Reads from and writes to" 21 | } 22 | 23 | views { 24 | component webapp { 25 | include * 26 | autoLayout lr 27 | } 28 | } 29 | 30 | } 31 | ``` 32 | 33 | This DSL defines a component view for the container `webapp`, and `include *` includes the default set of model elements for the view. 34 | 35 | [![](example-1.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/component-view/example-1.dsl) 36 | 37 | Component views can be rendered using the Structurizr cloud service/on-premises installation or exported to a number of other formats via the [Structurizr CLI export command](https://github.com/structurizr/cli/blob/master/docs/export.md). 38 | 39 | ## Links 40 | 41 | - [DSL language reference - component](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#component-view) -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/AbstractParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.apache.hc.client5.http.classic.methods.HttpGet; 4 | import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; 5 | import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; 6 | import org.apache.hc.client5.http.impl.classic.HttpClients; 7 | import org.apache.hc.core5.http.io.entity.EntityUtils; 8 | 9 | import java.util.regex.Pattern; 10 | 11 | abstract class AbstractParser { 12 | 13 | private static final int HTTP_OK_STATUS = 200; 14 | 15 | private static final Pattern VIEW_KEY_PATTERN = Pattern.compile("[\\w-]+"); 16 | 17 | void validateViewKey(String key) { 18 | if (!VIEW_KEY_PATTERN.matcher(key).matches()) { 19 | throw new RuntimeException("View keys can only contain the following characters: a-zA-0-9_-"); 20 | } 21 | } 22 | 23 | String removeNonWordCharacters(String name) { 24 | return name.replaceAll("\\W", ""); 25 | } 26 | 27 | protected String readFromUrl(String url) { 28 | try (CloseableHttpClient httpClient = HttpClients.createSystem()) { 29 | HttpGet httpGet = new HttpGet(url); 30 | CloseableHttpResponse response = httpClient.execute(httpGet); 31 | 32 | if (response.getCode() == HTTP_OK_STATUS) { 33 | return EntityUtils.toString(response.getEntity()); 34 | } 35 | } catch (Exception ioe) { 36 | ioe.printStackTrace(); 37 | } 38 | 39 | return ""; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /docs/cookbook/amazon-web-services/example-1.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" "" "Spring Boot" 7 | database = container "Database" "" "Relational database schema" 8 | } 9 | 10 | u -> webapp "Uses" 11 | webapp -> database "Reads from and writes to" 12 | 13 | live = deploymentEnvironment "Live" { 14 | deploymentNode "Amazon Web Services" { 15 | deploymentNode "US-East-1" { 16 | route53 = infrastructureNode "Route 53" 17 | elb = infrastructureNode "Elastic Load Balancer" 18 | 19 | deploymentNode "Amazon EC2" { 20 | deploymentNode "Ubuntu Server" { 21 | webApplicationInstance = containerInstance webapp 22 | } 23 | } 24 | 25 | deploymentNode "Amazon RDS" { 26 | deploymentNode "MySQL" { 27 | containerInstance database 28 | } 29 | } 30 | } 31 | } 32 | 33 | route53 -> elb "Forwards requests to" "HTTPS" 34 | elb -> webApplicationInstance "Forwards requests to" "HTTPS" 35 | } 36 | } 37 | 38 | views { 39 | deployment s live { 40 | include * 41 | autoLayout lr 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /examples/big-bank-plc/model/people-and-software-systems.dsl: -------------------------------------------------------------------------------- 1 | customer = person "Personal Banking Customer" "A customer of the bank, with personal bank accounts." "Customer" 2 | 3 | enterprise "Big Bank plc" { 4 | supportStaff = person "Customer Service Staff" "Customer service staff within the bank." "Bank Staff" 5 | backoffice = person "Back Office Staff" "Administration and support staff within the bank." "Bank Staff" 6 | 7 | mainframe = softwaresystem "Mainframe Banking System" "Stores all of the core banking information about customers, accounts, transactions, etc." "Existing System" 8 | email = softwaresystem "E-mail System" "The internal Microsoft Exchange e-mail system." "Existing System" 9 | atm = softwaresystem "ATM" "Allows customers to withdraw cash." "Existing System" 10 | 11 | internetBankingSystem = softwaresystem "Internet Banking System" "Allows customers to view information about their bank accounts, and make payments." { 12 | !include "internet-banking-system/${INTERNET_BANKING_SYSTEM_INCLUDE}" 13 | } 14 | } 15 | 16 | # relationships between people and software systems 17 | customer -> internetBankingSystem "Views account balances, and makes payments using" 18 | internetBankingSystem -> mainframe "Gets account information from, and makes payments using" 19 | internetBankingSystem -> email "Sends e-mail using" 20 | email -> customer "Sends e-mails to" 21 | customer -> supportStaff "Asks questions to" "Telephone" 22 | supportStaff -> mainframe "Uses" 23 | customer -> atm "Withdraws cash using" 24 | atm -> mainframe "Uses" 25 | backoffice -> mainframe "Uses" -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CustomViewParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.view.CustomView; 5 | 6 | final class CustomViewParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "custom [key] [title] [description] {"; 9 | 10 | private static final String VIEW_TYPE = "Custom"; 11 | 12 | private static final int KEY_INDEX = 1; 13 | private static final int TITLE_INDEX = 2; 14 | private static final int DESCRIPTION_INDEX = 3; 15 | 16 | CustomView parse(DslContext context, Tokens tokens) { 17 | // custom [key] [title] [description] 18 | 19 | if (tokens.hasMoreThan(DESCRIPTION_INDEX)) { 20 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 21 | } 22 | 23 | Workspace workspace = context.getWorkspace(); 24 | String key = ""; 25 | String title = ""; 26 | String description = ""; 27 | 28 | if (tokens.includes(KEY_INDEX)) { 29 | key = tokens.get(KEY_INDEX); 30 | } else { 31 | key = VIEW_TYPE + (context.getWorkspace().getViews().getCustomViews().size() + 1); 32 | } 33 | validateViewKey(key); 34 | 35 | if (tokens.includes(TITLE_INDEX)) { 36 | title = tokens.get(TITLE_INDEX); 37 | } 38 | 39 | if (tokens.includes(DESCRIPTION_INDEX)) { 40 | description = tokens.get(DESCRIPTION_INDEX); 41 | } 42 | 43 | CustomView view = workspace.getViews().createCustomView(key, title, description); 44 | 45 | return view; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/IdentifierScopeParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class IdentifierScopeParserTests extends AbstractTests { 9 | 10 | private IdentifierScopeParser parser = new IdentifierScopeParser(); 11 | 12 | @Test 13 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 14 | try { 15 | parser.parse(context(), tokens("!identifiers", "hierarchical", "extra")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Too many tokens, expected: !identifiers <flat|hierarchical>", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parse_ThrowsAnException_WhenNoScopeIsSpecified() { 24 | try { 25 | parser.parse(context(), tokens("!identifiers")); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: !identifiers <flat|hierarchical>", e.getMessage()); 29 | } 30 | } 31 | 32 | @Test 33 | void test_parse_SetsTheScope_WhenLocalIsSpecified() { 34 | IdentifierScope scope = parser.parse(context(), tokens("!identifiers", "hierarchical")); 35 | 36 | assertEquals(IdentifierScope.Hierarchical, scope); 37 | } 38 | 39 | @Test 40 | void test_parse_SetsTheScope_WhenGlobalIsSpecified() { 41 | IdentifierScope scope = parser.parse(context(), tokens("!identifiers", "flat")); 42 | 43 | assertEquals(IdentifierScope.Flat, scope); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CustomViewExpressionParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.CustomElement; 4 | import com.structurizr.model.Element; 5 | 6 | import java.util.LinkedHashSet; 7 | import java.util.Set; 8 | 9 | import static com.structurizr.dsl.StructurizrDslExpressions.ELEMENT_TYPE_EQUALS_EXPRESSION; 10 | 11 | final class CustomViewExpressionParser extends AbstractExpressionParser { 12 | 13 | @Override 14 | protected Set<Element> evaluateElementTypeExpression(String expr, DslContext context) { 15 | Set<Element> elements = new LinkedHashSet<>(); 16 | 17 | String type = expr.substring(ELEMENT_TYPE_EQUALS_EXPRESSION.length()); 18 | switch (type.toLowerCase()) { 19 | case "custom": 20 | context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof CustomElement).forEach(elements::add); 21 | break; 22 | default: 23 | throw new RuntimeException("The element type of \"" + type + "\" is not valid for this view"); 24 | } 25 | 26 | return elements; 27 | } 28 | 29 | protected Set<Element> findAfferentCouplings(Element element) { 30 | Set<Element> elements = new LinkedHashSet<>(); 31 | 32 | elements.addAll(findAfferentCouplings(element, CustomElement.class)); 33 | 34 | return elements; 35 | } 36 | 37 | protected Set<Element> findEfferentCouplings(Element element) { 38 | Set<Element> elements = new LinkedHashSet<>(); 39 | 40 | elements.addAll(findEfferentCouplings(element, CustomElement.class)); 41 | 42 | return elements; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ViewContentParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Relationship; 4 | import com.structurizr.view.View; 5 | 6 | import static com.structurizr.dsl.StructurizrDslExpressions.*; 7 | 8 | abstract class ViewContentParser extends AbstractParser { 9 | 10 | protected static final String WILDCARD = "*"; 11 | protected static final String ELEMENT_WILDCARD = "element==*"; 12 | protected static final String RELATIONSHIP_WILDCARD = "relationship==*"; 13 | 14 | protected boolean isExpression(String token) { 15 | token = token.toLowerCase(); 16 | 17 | return 18 | token.startsWith(ELEMENT_TYPE_EQUALS_EXPRESSION.toLowerCase()) || 19 | token.startsWith(ELEMENT_TAG_EQUALS_EXPRESSION.toLowerCase()) || 20 | token.startsWith(ELEMENT_TAG_NOT_EQUALS_EXPRESSION.toLowerCase()) || 21 | token.startsWith(RELATIONSHIP) || token.endsWith(RELATIONSHIP) || token.contains(RELATIONSHIP) || 22 | token.endsWith(ELEMENT_EQUALS_EXPRESSION) || 23 | token.startsWith(RELATIONSHIP_TAG_EQUALS_EXPRESSION.toLowerCase()) || 24 | token.startsWith(RELATIONSHIP_TAG_NOT_EQUALS_EXPRESSION.toLowerCase()) || 25 | token.startsWith(RELATIONSHIP_SOURCE_EQUALS_EXPRESSION.toLowerCase()) || 26 | token.startsWith(RELATIONSHIP_DESTINATION_EQUALS_EXPRESSION.toLowerCase()) || 27 | token.startsWith(RELATIONSHIP_EQUALS_EXPRESSION); 28 | } 29 | 30 | protected void removeRelationshipFromView(Relationship relationship, View view) { 31 | view.remove(relationship); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/RefParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Element; 4 | import com.structurizr.model.Person; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | class RefParserTests extends AbstractTests { 10 | 11 | private RefParser parser = new RefParser(); 12 | 13 | @Test 14 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 15 | try { 16 | parser.parse(context(), tokens("ref", "name", "tokens")); 17 | fail(); 18 | } catch (Exception e) { 19 | assertEquals("Too many tokens, expected: ref <canonical name>", e.getMessage()); 20 | } 21 | } 22 | 23 | @Test 24 | void test_parse_ThrowsAnException_WhenTheNameIsNotSpecified() { 25 | try { 26 | parser.parse(context(), tokens("ref")); 27 | fail(); 28 | } catch (Exception e) { 29 | assertEquals("Expected: ref <canonical name>", e.getMessage()); 30 | } 31 | } 32 | 33 | @Test 34 | void test_parse_ThrowsAnException_WhenTheNamedElementCannotBeFound() { 35 | try { 36 | parser.parse(context(), tokens("ref", "Person://User")); 37 | fail(); 38 | } catch (Exception e) { 39 | assertEquals("Person://User could not be found", e.getMessage()); 40 | } 41 | } 42 | 43 | @Test 44 | void test_parse_FindTheNamedElement() { 45 | Person user = workspace.getModel().addPerson("User"); 46 | Element element = parser.parse(context(), tokens("ref", "Person://User")); 47 | 48 | assertSame(user, element); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /examples/big-bank-plc/model/internet-banking-system/details.dsl: -------------------------------------------------------------------------------- 1 | singlePageApplication = container "Single-Page Application" "Provides all of the Internet banking functionality to customers via their web browser." "JavaScript and Angular" "Web Browser" 2 | mobileApp = container "Mobile App" "Provides a limited subset of the Internet banking functionality to customers via their mobile device." "Xamarin" "Mobile App" 3 | webApplication = container "Web Application" "Delivers the static content and the Internet banking single page application." "Java and Spring MVC" 4 | apiApplication = container "API Application" "Provides Internet banking functionality via a JSON/HTTPS API." "Java and Spring MVC" { 5 | signinController = component "Sign In Controller" "Allows users to sign in to the Internet Banking System." "Spring MVC Rest Controller" 6 | accountsSummaryController = component "Accounts Summary Controller" "Provides customers with a summary of their bank accounts." "Spring MVC Rest Controller" 7 | resetPasswordController = component "Reset Password Controller" "Allows users to reset their passwords with a single use URL." "Spring MVC Rest Controller" 8 | securityComponent = component "Security Component" "Provides functionality related to signing in, changing passwords, etc." "Spring Bean" 9 | mainframeBankingSystemFacade = component "Mainframe Banking System Facade" "A facade onto the mainframe banking system." "Spring Bean" 10 | emailComponent = component "E-mail Component" "Sends e-mails to users." "Spring Bean" 11 | } 12 | database = container "Database" "Stores user registration information, hashed authentication credentials, access logs, etc." "Oracle Database Schema" "Database" 13 | 14 | !docs docs 15 | !adrs adrs -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StaticViewAnimationStepParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Element; 4 | import com.structurizr.view.StaticView; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | final class StaticViewAnimationStepParser extends AbstractParser { 10 | 11 | void parse(StaticViewDslContext context, Tokens tokens) { 12 | // animationStep <identifier> [identifier...] 13 | 14 | if (!tokens.includes(1)) { 15 | throw new RuntimeException("Expected: animationStep <identifier> [identifier...]"); 16 | } 17 | 18 | parse(context, context.getView(), tokens, 1); 19 | } 20 | 21 | void parse(StaticViewAnimationDslContext context, Tokens tokens) { 22 | // <identifier> [identifier...] 23 | 24 | if (!tokens.includes(0)) { 25 | throw new RuntimeException("Expected: <identifier> [identifier...]"); 26 | } 27 | 28 | parse(context, context.getView(), tokens, 0); 29 | } 30 | 31 | void parse(DslContext context, StaticView view, Tokens tokens, int startIndex) { 32 | // <identifier> [identifier...] 33 | 34 | List<Element> elements = new ArrayList<>(); 35 | 36 | for (int i = startIndex; i < tokens.size(); i++) { 37 | String elementIdentifier = tokens.get(i); 38 | 39 | Element element = context.getElement(elementIdentifier); 40 | if (element == null) { 41 | throw new RuntimeException("The element \"" + elementIdentifier + "\" does not exist"); 42 | } 43 | 44 | elements.add(element); 45 | } 46 | 47 | view.addAnimation(elements.toArray(new Element[0])); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CustomViewAnimationStepParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.CustomElement; 4 | import com.structurizr.model.Element; 5 | import com.structurizr.view.CustomView; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | final class CustomViewAnimationStepParser extends AbstractParser { 11 | 12 | void parse(CustomViewDslContext context, Tokens tokens) { 13 | // animationStep <identifier> [identifier...] 14 | 15 | if (!tokens.includes(1)) { 16 | throw new RuntimeException("Expected: animationStep <identifier> [identifier...]"); 17 | } 18 | 19 | parse(context, context.getCustomView(), tokens, 1); 20 | } 21 | 22 | void parse(CustomViewAnimationDslContext context, Tokens tokens) { 23 | // <identifier> [identifier...] 24 | 25 | if (!tokens.includes(0)) { 26 | throw new RuntimeException("Expected: <identifier> [identifier...]"); 27 | } 28 | 29 | parse(context, context.getView(), tokens, 0); 30 | } 31 | 32 | void parse(DslContext context, CustomView view, Tokens tokens, int startIndex) { 33 | List<CustomElement> elements = new ArrayList<>(); 34 | 35 | for (int i = startIndex; i < tokens.size(); i++) { 36 | String elementIdentifier = tokens.get(i); 37 | 38 | Element element = context.getElement(elementIdentifier); 39 | if (element == null) { 40 | throw new RuntimeException("The element \"" + elementIdentifier + "\" does not exist"); 41 | } 42 | 43 | if (element instanceof CustomElement) { 44 | elements.add((CustomElement)element); 45 | } 46 | } 47 | 48 | view.addAnimation(elements.toArray(new CustomElement[0])); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/CustomElementParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.CustomElement; 4 | import com.structurizr.model.Location; 5 | import com.structurizr.model.Person; 6 | 7 | final class CustomElementParser extends AbstractParser { 8 | 9 | private static final String GRAMMAR = "element <name> [metadata] [description] [tags]"; 10 | 11 | private final static int NAME_INDEX = 1; 12 | private final static int METADATA_INDEX = 2; 13 | private final static int DESCRIPTION_INDEX = 3; 14 | private final static int TAGS_INDEX = 4; 15 | 16 | CustomElement parse(GroupableDslContext context, Tokens tokens) { 17 | // element <name> [metadata] [description] [tags] 18 | 19 | if (tokens.hasMoreThan(TAGS_INDEX)) { 20 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 21 | } 22 | 23 | if (!tokens.includes(NAME_INDEX)) { 24 | throw new RuntimeException("Expected: " + GRAMMAR); 25 | } 26 | 27 | String name = tokens.get(NAME_INDEX); 28 | 29 | String metadata = ""; 30 | if (tokens.includes(METADATA_INDEX)) { 31 | metadata = tokens.get(METADATA_INDEX); 32 | } 33 | 34 | String description = ""; 35 | if (tokens.includes(DESCRIPTION_INDEX)) { 36 | description = tokens.get(DESCRIPTION_INDEX); 37 | } 38 | 39 | CustomElement customElement = context.getWorkspace().getModel().addCustomElement(name, metadata, description); 40 | 41 | if (tokens.includes(TAGS_INDEX)) { 42 | String tags = tokens.get(TAGS_INDEX); 43 | customElement.addTags(tags.split(",")); 44 | } 45 | 46 | if (context.hasGroup()) { 47 | customElement.setGroup(context.getGroup().getName()); 48 | } 49 | 50 | return customElement; 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/InfrastructureNodeParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.DeploymentNode; 4 | import com.structurizr.model.InfrastructureNode; 5 | 6 | final class InfrastructureNodeParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "infrastructureNode <name> [description] [technology] [tags]"; 9 | 10 | private static final int NAME_INDEX = 1; 11 | private static final int DESCRIPTION_INDEX = 2; 12 | private static final int TECHNOLOGY_INDEX = 3; 13 | private static final int TAGS_INDEX = 4; 14 | 15 | InfrastructureNode parse(DeploymentNodeDslContext context, Tokens tokens) { 16 | // infrastructureNode <name> [description] [technology] [tags] 17 | 18 | if (tokens.hasMoreThan(TAGS_INDEX)) { 19 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 20 | } 21 | 22 | if (!tokens.includes(NAME_INDEX)) { 23 | throw new RuntimeException("Expected: " + GRAMMAR); 24 | } 25 | 26 | DeploymentNode deploymentNode = context.getDeploymentNode(); 27 | InfrastructureNode infrastructureNode; 28 | String name = tokens.get(NAME_INDEX); 29 | 30 | String description = ""; 31 | if (tokens.includes(DESCRIPTION_INDEX)) { 32 | description = tokens.get(DESCRIPTION_INDEX); 33 | } 34 | 35 | String technology = ""; 36 | if (tokens.includes(TECHNOLOGY_INDEX)) { 37 | technology = tokens.get(TECHNOLOGY_INDEX); 38 | } 39 | 40 | infrastructureNode = deploymentNode.addInfrastructureNode(name, description, technology); 41 | 42 | if (tokens.includes(TAGS_INDEX)) { 43 | String tags = tokens.get(TAGS_INDEX); 44 | infrastructureNode.addTags(tags.split(",")); 45 | } 46 | 47 | return infrastructureNode; 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/PersonParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Location; 4 | import com.structurizr.model.Person; 5 | 6 | final class PersonParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "person <name> [description] [tags]"; 9 | 10 | private final static int NAME_INDEX = 1; 11 | private final static int DESCRIPTION_INDEX = 2; 12 | private final static int TAGS_INDEX = 3; 13 | 14 | Person parse(GroupableDslContext context, Tokens tokens) { 15 | // person <name> [description] [tags] 16 | 17 | if (tokens.hasMoreThan(TAGS_INDEX)) { 18 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 19 | } 20 | 21 | if (!tokens.includes(NAME_INDEX)) { 22 | throw new RuntimeException("Expected: " + GRAMMAR); 23 | } 24 | 25 | Person person = null; 26 | String name = tokens.get(NAME_INDEX); 27 | 28 | if (context.isExtendingWorkspace()) { 29 | person = context.getWorkspace().getModel().getPersonWithName(name); 30 | } 31 | 32 | if (person == null) { 33 | person = context.getWorkspace().getModel().addPerson(name); 34 | } 35 | 36 | String description = ""; 37 | if (tokens.includes(DESCRIPTION_INDEX)) { 38 | description = tokens.get(DESCRIPTION_INDEX); 39 | person.setDescription(description); 40 | } 41 | 42 | if (tokens.includes(TAGS_INDEX)) { 43 | String tags = tokens.get(TAGS_INDEX); 44 | person.addTags(tags.split(",")); 45 | } 46 | 47 | if (context instanceof EnterpriseDslContext) { 48 | person.setLocation(Location.Internal); 49 | } 50 | 51 | if (context.hasGroup()) { 52 | person.setGroup(context.getGroup().getName()); 53 | context.getGroup().addElement(person); 54 | } 55 | 56 | return person; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /docs/cookbook/deployment-view/README.md: -------------------------------------------------------------------------------- 1 | # Deployment view 2 | 3 | A [deployment view](https://c4model.com/#DeploymentDiagram) allows you to show how software systems and containers are deployed, by showing the mapping of software system and container _instances_ onto deployment nodes. 4 | 5 | ``` 6 | workspace { 7 | 8 | model { 9 | u = person "User" 10 | s = softwareSystem "Software System" { 11 | webapp = container "Web Application" "" "Spring Boot" 12 | database = container "Database" "" "Relational database schema" 13 | } 14 | 15 | u -> webapp "Uses" 16 | webapp -> database "Reads from and writes to" 17 | 18 | development = deploymentEnvironment "Development" { 19 | deploymentNode "Developer Laptop" { 20 | containerInstance webapp 21 | deploymentNode "MySQL" { 22 | containerInstance database 23 | } 24 | } 25 | } 26 | } 27 | 28 | views { 29 | deployment * development { 30 | include * 31 | autoLayout lr 32 | } 33 | } 34 | 35 | } 36 | ``` 37 | 38 | This DSL defines a deployment environment named `Development`, with instances of the `webapp` and `database` containers deployed onto some deployment nodes. It also defines a deployment view for this deployment environment, and `include *` includes the default set of model elements for the view. 39 | 40 | [![](example-1.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/deployment-view/example-1.dsl) 41 | 42 | Deployment views can be rendered using the Structurizr cloud service/on-premises installation or exported to a number of other formats via the [Structurizr CLI export command](https://github.com/structurizr/cli/blob/master/docs/export.md). 43 | 44 | ## Links 45 | 46 | - [DSL language reference - deployment](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#deployment-view) -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ImplicitRelationshipParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.*; 4 | 5 | final class ImplicitRelationshipParser extends AbstractRelationshipParser { 6 | 7 | private static final String GRAMMAR = "-> <identifier> [description] [technology] [tags]"; 8 | 9 | private static final int DESTINATION_IDENTIFIER_INDEX = 1; 10 | private final static int DESCRIPTION_INDEX = 2; 11 | private final static int TECHNOLOGY_INDEX = 3; 12 | private final static int TAGS_INDEX = 4; 13 | 14 | Relationship parse(ModelItemDslContext context, Tokens tokens) { 15 | // -> <identifier> [description] [technology] [tags] 16 | 17 | if (tokens.hasMoreThan(TAGS_INDEX)) { 18 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 19 | } 20 | 21 | if (!tokens.includes(DESTINATION_IDENTIFIER_INDEX)) { 22 | throw new RuntimeException("Expected: " + GRAMMAR); 23 | } 24 | 25 | String destinationId = tokens.get(DESTINATION_IDENTIFIER_INDEX); 26 | 27 | Element sourceElement = (Element)context.getModelItem(); 28 | Element destinationElement = context.getElement(destinationId); 29 | 30 | if (destinationElement == null) { 31 | throw new RuntimeException("The destination element \"" + destinationId + "\" does not exist"); 32 | } 33 | 34 | String description = ""; 35 | if (tokens.includes(DESCRIPTION_INDEX)) { 36 | description = tokens.get(DESCRIPTION_INDEX); 37 | } 38 | 39 | String technology = ""; 40 | if (tokens.includes(TECHNOLOGY_INDEX)) { 41 | technology = tokens.get(TECHNOLOGY_INDEX); 42 | } 43 | 44 | String[] tags = new String[0]; 45 | if (tokens.includes(TAGS_INDEX)) { 46 | tags = tokens.get(TAGS_INDEX).split(","); 47 | } 48 | 49 | return createRelationship(sourceElement, description, technology, tags, destinationElement); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/IncludeParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Files; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | final class IncludeParser extends AbstractParser { 11 | 12 | private static final String GRAMMAR = "!include <file|url>"; 13 | 14 | private static final int FILE_INDEX = 1; 15 | 16 | void parse(IncludedDslContext context, Tokens tokens) { 17 | // !include <file|url> 18 | 19 | if (tokens.hasMoreThan(FILE_INDEX)) { 20 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 21 | } 22 | 23 | if (!tokens.includes(FILE_INDEX)) { 24 | throw new RuntimeException("Expected: " + GRAMMAR); 25 | } 26 | 27 | String source = tokens.get(FILE_INDEX); 28 | if (source.startsWith("https://")) { 29 | String dsl = readFromUrl(source); 30 | List<String> lines = Arrays.asList(dsl.split("\n")); 31 | context.setLines(lines); 32 | context.setFile(context.getFile()); 33 | } else { 34 | if (context.getParentFile() != null) { 35 | File file = new File(context.getParentFile().getParent(), source); 36 | 37 | try { 38 | if (!file.exists()) { 39 | throw new RuntimeException(file.getCanonicalPath() + " could not be found"); 40 | } 41 | 42 | if (file.isDirectory()) { 43 | throw new RuntimeException(file.getCanonicalPath() + " should be a single file"); 44 | } 45 | 46 | List<String> lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); 47 | context.setLines(lines); 48 | context.setFile(file); 49 | } catch (IOException e) { 50 | throw new RuntimeException(e.getMessage()); 51 | } 52 | } 53 | } 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/ConstantParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.fail; 7 | 8 | class ConstantParserTests extends AbstractTests { 9 | 10 | private ConstantParser parser = new ConstantParser(); 11 | 12 | @Test 13 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 14 | try { 15 | parser.parse(context(), tokens("!constant", "name", "value", "extra")); 16 | fail(); 17 | } catch (Exception e) { 18 | assertEquals("Too many tokens, expected: !constant <name> <value>", e.getMessage()); 19 | } 20 | } 21 | 22 | @Test 23 | void test_parse_ThrowsAnException_WhenNoNameOrValueIsSpecified() { 24 | try { 25 | parser.parse(context(), tokens("!constant")); 26 | fail(); 27 | } catch (Exception e) { 28 | assertEquals("Expected: !constant <name> <value>", e.getMessage()); 29 | } 30 | } 31 | 32 | @Test 33 | void test_parse_ThrowsAnException_WhenNoValueIsSpecified() { 34 | try { 35 | parser.parse(context(), tokens("!constant", "name")); 36 | fail(); 37 | } catch (Exception e) { 38 | assertEquals("Expected: !constant <name> <value>", e.getMessage()); 39 | } 40 | } 41 | 42 | @Test 43 | void test_parse_ThrowsAnException_WhenNameContainsDisallowedCharacters() { 44 | try { 45 | parser.parse(context(), tokens("!constant", "${NAME}", "value")); 46 | fail(); 47 | } catch (Exception e) { 48 | assertEquals("Constant names must only contain the following characters: a-zA-Z0-9-_.", e.getMessage()); 49 | } 50 | } 51 | 52 | @Test 53 | void test_parse_CreatesAConstant() { 54 | Constant constant = parser.parse(context(), tokens("!constant", "name", "value")); 55 | assertEquals("name", constant.getName()); 56 | assertEquals("value", constant.getValue()); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/AbstractRelationshipParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.*; 4 | 5 | abstract class AbstractRelationshipParser extends AbstractParser { 6 | 7 | protected Relationship createRelationship(Element sourceElement, String description, String technology, String[] tags, Element destinationElement) { 8 | Relationship relationship = null; 9 | 10 | if (sourceElement instanceof CustomElement) { 11 | relationship = ((CustomElement)sourceElement).uses(destinationElement, description, technology, null, tags); 12 | } else if (destinationElement instanceof CustomElement) { 13 | relationship = sourceElement.uses((CustomElement)destinationElement, description, technology, null, tags); 14 | } else if (sourceElement instanceof StaticStructureElement && destinationElement instanceof StaticStructureElement) { 15 | relationship = ((StaticStructureElement)sourceElement).uses((StaticStructureElement)destinationElement, description, technology, null, tags); 16 | } else if (sourceElement instanceof DeploymentNode && destinationElement instanceof DeploymentNode) { 17 | relationship = ((DeploymentNode)sourceElement).uses((DeploymentNode)destinationElement, description, technology, null, tags); 18 | } else if (sourceElement instanceof InfrastructureNode && destinationElement instanceof DeploymentElement) { 19 | relationship = ((InfrastructureNode)sourceElement).uses((DeploymentElement)destinationElement, description, technology, null, tags); 20 | } else if (sourceElement instanceof StaticStructureElementInstance && destinationElement instanceof InfrastructureNode) { 21 | relationship = ((StaticStructureElementInstance)sourceElement).uses((InfrastructureNode)destinationElement, description, technology, null, tags); 22 | } else { 23 | throw new RuntimeException("A relationship between \"" + sourceElement.getCanonicalName() + "\" and \"" + destinationElement.getCanonicalName() + "\" is not permitted"); 24 | } 25 | 26 | return relationship; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SoftwareSystemParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Location; 4 | import com.structurizr.model.SoftwareSystem; 5 | 6 | final class SoftwareSystemParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "softwareSystem <name> [description] [tags]"; 9 | 10 | private final static int NAME_INDEX = 1; 11 | private final static int DESCRIPTION_INDEX = 2; 12 | private final static int TAGS_INDEX = 3; 13 | 14 | SoftwareSystem parse(GroupableDslContext context, Tokens tokens) { 15 | // softwareSystem <name> [description] [tags] 16 | 17 | if (tokens.hasMoreThan(TAGS_INDEX)) { 18 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 19 | } 20 | 21 | if (!tokens.includes(NAME_INDEX)) { 22 | throw new RuntimeException("Expected: " + GRAMMAR); 23 | } 24 | 25 | SoftwareSystem softwareSystem = null; 26 | String name = tokens.get(NAME_INDEX); 27 | 28 | if (context.isExtendingWorkspace()) { 29 | softwareSystem = context.getWorkspace().getModel().getSoftwareSystemWithName(name); 30 | } 31 | 32 | if (softwareSystem == null) { 33 | softwareSystem = context.getWorkspace().getModel().addSoftwareSystem(name); 34 | } 35 | 36 | String description = ""; 37 | if (tokens.includes(DESCRIPTION_INDEX)) { 38 | description = tokens.get(DESCRIPTION_INDEX); 39 | softwareSystem.setDescription(description); 40 | } 41 | 42 | if (tokens.includes(TAGS_INDEX)) { 43 | String tags = tokens.get(TAGS_INDEX); 44 | softwareSystem.addTags(tags.split(",")); 45 | } 46 | 47 | if (context instanceof EnterpriseDslContext) { 48 | softwareSystem.setLocation(Location.Internal); 49 | } 50 | 51 | if (context.hasGroup()) { 52 | softwareSystem.setGroup(context.getGroup().getName()); 53 | context.getGroup().addElement(softwareSystem); 54 | } 55 | 56 | return softwareSystem; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ComponentParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Component; 4 | import com.structurizr.model.Container; 5 | 6 | final class ComponentParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "component <name> [description] [technology] [tags]"; 9 | 10 | private final static int NAME_INDEX = 1; 11 | private final static int DESCRIPTION_INDEX = 2; 12 | private final static int TECHNOLOGY_INDEX = 3; 13 | private final static int TAGS_INDEX = 4; 14 | 15 | Component parse(ContainerDslContext context, Tokens tokens) { 16 | // component <name> [description] [technology] [tags] 17 | 18 | if (tokens.hasMoreThan(TAGS_INDEX)) { 19 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 20 | } 21 | 22 | if (!tokens.includes(NAME_INDEX)) { 23 | throw new RuntimeException("Expected: " + GRAMMAR); 24 | } 25 | 26 | Container container = context.getContainer(); 27 | Component component = null; 28 | String name = tokens.get(NAME_INDEX); 29 | 30 | if (context.isExtendingWorkspace()) { 31 | component = container.getComponentWithName(name); 32 | } 33 | 34 | if (component == null) { 35 | component = container.addComponent(name); 36 | } 37 | 38 | if (tokens.includes(DESCRIPTION_INDEX)) { 39 | String description = tokens.get(DESCRIPTION_INDEX); 40 | component.setDescription(description); 41 | } 42 | 43 | if (tokens.includes(TECHNOLOGY_INDEX)) { 44 | String technology = tokens.get(TECHNOLOGY_INDEX); 45 | component.setTechnology(technology); 46 | } 47 | 48 | if (tokens.includes(TAGS_INDEX)) { 49 | String tags = tokens.get(TAGS_INDEX); 50 | component.addTags(tags.split(",")); 51 | } 52 | 53 | if (context.hasGroup()) { 54 | component.setGroup(context.getGroup().getName()); 55 | context.getGroup().addElement(component); 56 | } 57 | 58 | return component; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ContainerParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.Container; 4 | import com.structurizr.model.SoftwareSystem; 5 | 6 | final class ContainerParser extends AbstractParser { 7 | 8 | private static final String GRAMMAR = "container <name> [description] [technology] [tags]"; 9 | 10 | private final static int NAME_INDEX = 1; 11 | private final static int DESCRIPTION_INDEX = 2; 12 | private final static int TECHNOLOGY_INDEX = 3; 13 | private final static int TAGS_INDEX = 4; 14 | 15 | Container parse(SoftwareSystemDslContext context, Tokens tokens) { 16 | // container <name> [description] [technology] [tags] 17 | 18 | if (tokens.hasMoreThan(TAGS_INDEX)) { 19 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 20 | } 21 | 22 | if (!tokens.includes(NAME_INDEX)) { 23 | throw new RuntimeException("Expected: " + GRAMMAR); 24 | } 25 | 26 | SoftwareSystem softwareSystem = context.getSoftwareSystem(); 27 | Container container = null; 28 | String name = tokens.get(NAME_INDEX); 29 | 30 | if (context.isExtendingWorkspace()) { 31 | container = softwareSystem.getContainerWithName(name); 32 | } 33 | 34 | if (container == null) { 35 | container = softwareSystem.addContainer(name); 36 | } 37 | 38 | if (tokens.includes(DESCRIPTION_INDEX)) { 39 | String description = tokens.get(DESCRIPTION_INDEX); 40 | container.setDescription(description); 41 | } 42 | 43 | if (tokens.includes(TECHNOLOGY_INDEX)) { 44 | String technology = tokens.get(TECHNOLOGY_INDEX); 45 | container.setTechnology(technology); 46 | } 47 | 48 | if (tokens.includes(TAGS_INDEX)) { 49 | String tags = tokens.get(TAGS_INDEX); 50 | container.addTags(tags.split(",")); 51 | } 52 | 53 | if (context.hasGroup()) { 54 | container.setGroup(context.getGroup().getName()); 55 | context.getGroup().addElement(container); 56 | } 57 | 58 | return container; 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/AdrsParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.documentation.AdrToolsImporter; 5 | import com.structurizr.model.SoftwareSystem; 6 | 7 | import java.io.File; 8 | 9 | final class AdrsParser extends AbstractParser { 10 | 11 | private static final String GRAMMAR = "!adrs <path>"; 12 | 13 | private static final int PATH_INDEX = 1; 14 | 15 | void parse(WorkspaceDslContext context, File file, Tokens tokens) { 16 | parse(context.getWorkspace(), null, file, tokens); 17 | } 18 | 19 | void parse(SoftwareSystemDslContext context, File file, Tokens tokens) { 20 | parse(context.getWorkspace(), context.getSoftwareSystem(), file, tokens); 21 | } 22 | 23 | private void parse(Workspace workspace, SoftwareSystem softwareSystem, File file, Tokens tokens) { 24 | // !adrs <path> 25 | 26 | if (tokens.hasMoreThan(PATH_INDEX)) { 27 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 28 | } 29 | 30 | if (!tokens.includes(PATH_INDEX)) { 31 | throw new RuntimeException("Expected: " + GRAMMAR); 32 | } 33 | 34 | if (file != null) { 35 | File path = new File(file.getParentFile(), tokens.get(PATH_INDEX)); 36 | 37 | if (!path.exists()) { 38 | throw new RuntimeException("Documentation path " + path + " does not exist"); 39 | } 40 | 41 | if (!path.isDirectory()) { 42 | throw new RuntimeException("Documentation path " + path + " is not a directory"); 43 | } 44 | 45 | AdrToolsImporter adrToolsImporter = new AdrToolsImporter(workspace, path); 46 | try { 47 | if (softwareSystem == null) { 48 | adrToolsImporter.importArchitectureDecisionRecords(); 49 | } else { 50 | adrToolsImporter.importArchitectureDecisionRecords(softwareSystem); 51 | } 52 | } catch (Exception e) { 53 | throw new RuntimeException("Error importing ADRs from " + path.getAbsolutePath() + ": " + e.getMessage()); 54 | } 55 | } 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /docs/cookbook/amazon-web-services/example-2.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" "" "Spring Boot" 7 | database = container "Database" "" "Relational database schema" 8 | } 9 | 10 | u -> webapp "Uses" 11 | webapp -> database "Reads from and writes to" 12 | 13 | live = deploymentEnvironment "Live" { 14 | deploymentNode "Amazon Web Services" { 15 | tags "Amazon Web Services - Cloud" 16 | 17 | deploymentNode "US-East-1" { 18 | tags "Amazon Web Services - Region" 19 | 20 | route53 = infrastructureNode "Route 53" { 21 | tags "Amazon Web Services - Route 53" 22 | } 23 | elb = infrastructureNode "Elastic Load Balancer" { 24 | tags "Amazon Web Services - Elastic Load Balancing" 25 | } 26 | 27 | deploymentNode "Amazon EC2" { 28 | tags "Amazon Web Services - EC2" 29 | 30 | deploymentNode "Ubuntu Server" { 31 | webApplicationInstance = containerInstance webapp 32 | } 33 | } 34 | 35 | deploymentNode "Amazon RDS" { 36 | tags "Amazon Web Services - RDS" 37 | 38 | deploymentNode "MySQL" { 39 | tags "Amazon Web Services - RDS MySQL instance" 40 | 41 | containerInstance database 42 | } 43 | } 44 | } 45 | } 46 | 47 | route53 -> elb "Forwards requests to" "HTTPS" 48 | elb -> webApplicationInstance "Forwards requests to" "HTTPS" 49 | } 50 | } 51 | 52 | views { 53 | deployment s live { 54 | include * 55 | autoLayout lr 56 | } 57 | 58 | theme https://static.structurizr.com/themes/amazon-web-services-2020.04.30/theme.json 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DocsParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.documentation.AutomaticDocumentationTemplate; 5 | import com.structurizr.model.SoftwareSystem; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | final class DocsParser extends AbstractParser { 11 | 12 | private static final String GRAMMAR = "!docs <path>"; 13 | 14 | private static final int PATH_INDEX = 1; 15 | 16 | void parse(WorkspaceDslContext context, File file, Tokens tokens) { 17 | parse(context.getWorkspace(), null, file, tokens); 18 | } 19 | 20 | void parse(SoftwareSystemDslContext context, File file, Tokens tokens) { 21 | parse(context.getWorkspace(), context.getSoftwareSystem(), file, tokens); 22 | } 23 | 24 | private void parse(Workspace workspace, SoftwareSystem softwareSystem, File file, Tokens tokens) { 25 | // !docs <path> 26 | 27 | if (tokens.hasMoreThan(PATH_INDEX)) { 28 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 29 | } 30 | 31 | if (!tokens.includes(PATH_INDEX)) { 32 | throw new RuntimeException("Expected: " + GRAMMAR); 33 | } 34 | 35 | if (file != null) { 36 | File path = new File(file.getParentFile(), tokens.get(PATH_INDEX)); 37 | 38 | if (!path.exists()) { 39 | throw new RuntimeException("Documentation path " + path + " does not exist"); 40 | } 41 | 42 | if (!path.isDirectory()) { 43 | throw new RuntimeException("Documentation path " + path + " is not a directory"); 44 | } 45 | 46 | AutomaticDocumentationTemplate template = new AutomaticDocumentationTemplate(workspace); 47 | template.setRecursive(true); 48 | try { 49 | if (softwareSystem == null) { 50 | template.addSections(path); 51 | } else { 52 | template.addSections(softwareSystem, path); 53 | } 54 | template.addImages(path); 55 | } catch (IOException e) { 56 | throw new RuntimeException("Error importing documentation from " + path.getAbsolutePath() + ": " + e.getMessage()); 57 | } 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ContainerInstanceParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.*; 4 | 5 | final class ContainerInstanceParser extends AbstractParser { 6 | 7 | private static final String GRAMMAR = "containerInstance <identifier> [deploymentGroup|tags] [tags]"; 8 | 9 | private static final int IDENTIFIER_INDEX = 1; 10 | private static final int SECOND_TOKEN = 2; 11 | private static final int THIRD_TOKEN = 3; 12 | 13 | ContainerInstance parse(DeploymentNodeDslContext context, Tokens tokens) { 14 | // containerInstance <identifier> [tags] [group] 15 | 16 | if (tokens.hasMoreThan(THIRD_TOKEN)) { 17 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 18 | } 19 | 20 | if (!tokens.includes(IDENTIFIER_INDEX)) { 21 | throw new RuntimeException("Expected: " + GRAMMAR); 22 | } 23 | 24 | String containerIdentifier = tokens.get(IDENTIFIER_INDEX); 25 | 26 | Element element = context.getElement(containerIdentifier); 27 | if (element == null) { 28 | throw new RuntimeException("The container \"" + containerIdentifier + "\" does not exist"); 29 | } 30 | 31 | if (element instanceof Container) { 32 | DeploymentNode deploymentNode = context.getDeploymentNode(); 33 | 34 | String deploymentGroup = DeploymentElement.DEFAULT_DEPLOYMENT_GROUP; 35 | int tagsIndex = SECOND_TOKEN; 36 | 37 | if (tokens.includes(SECOND_TOKEN)) { 38 | String token = tokens.get(SECOND_TOKEN); 39 | 40 | if (context.getElement(token) instanceof DeploymentGroup) { 41 | deploymentGroup = context.getElement(token).getName(); 42 | tagsIndex = THIRD_TOKEN; 43 | } 44 | } 45 | 46 | ContainerInstance containerInstance = deploymentNode.add((Container)element, deploymentGroup); 47 | 48 | if (tokens.includes(tagsIndex)) { 49 | String tags = tokens.get(tagsIndex); 50 | containerInstance.addTags(tags.split(",")); 51 | } 52 | 53 | return containerInstance; 54 | } else { 55 | throw new RuntimeException("The element \"" + containerIdentifier + "\" is not a container"); 56 | } 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /docs/cookbook/element-styles/README.md: -------------------------------------------------------------------------------- 1 | # Element styles 2 | 3 | By default all elements are styled as grey boxes. 4 | 5 | [![](example-1.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/element-styles/example-1.dsl) 6 | 7 | ## Styling all elements 8 | 9 | To change the style for all elements, add an element style for the `Element` tag. 10 | 11 | ``` 12 | workspace { 13 | 14 | model { 15 | a = softwareSystem "A" 16 | b = softwareSystem "B" 17 | c = softwareSystem "C" 18 | 19 | a -> b 20 | b -> c 21 | } 22 | 23 | views { 24 | systemLandscape { 25 | include * 26 | autolayout lr 27 | } 28 | 29 | styles { 30 | element "Element" { 31 | background #1168bd 32 | color #ffffff 33 | shape RoundedBox 34 | } 35 | } 36 | } 37 | 38 | } 39 | ``` 40 | 41 | [![](example-2.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/element-styles/example-2.dsl) 42 | 43 | ## Styling individual elements 44 | 45 | To change the style of an individual element: 46 | 47 | 1. Tag the element. 48 | 2. Add an element style for that tag. 49 | 50 | ``` 51 | workspace { 52 | 53 | model { 54 | a = softwareSystem "A" { 55 | tags "Tag 1" 56 | } 57 | b = softwareSystem "B" 58 | c = softwareSystem "C" 59 | 60 | a -> b 61 | b -> c 62 | } 63 | 64 | views { 65 | systemLandscape { 66 | include * 67 | autolayout lr 68 | } 69 | 70 | styles { 71 | element "Tag 1" { 72 | background #1168bd 73 | color #ffffff 74 | shape RoundedBox 75 | } 76 | } 77 | } 78 | 79 | } 80 | ``` 81 | 82 | [![](example-3.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/element-styles/example-3.dsl) 83 | 84 | ## Notes 85 | 86 | Please note that element styles are designed to work with the Structurizr cloud service/on-premises installation, and may not be fully supported by the PlantUML, Mermaid, etc export formats. 87 | 88 | - [DSL language reference - styles - element](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#element-style) -------------------------------------------------------------------------------- /docs/cookbook/relationship-styles/README.md: -------------------------------------------------------------------------------- 1 | # Relationship styles 2 | 3 | By default all relationships are styled as dashed grey lines. 4 | 5 | [![](example-1.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/relationship-styles/example-1.dsl) 6 | 7 | ## Styling all relationships 8 | 9 | To change the style for all relationships, add a relationship style for the `Relationship` tag. 10 | 11 | ``` 12 | workspace { 13 | 14 | model { 15 | a = softwareSystem "A" 16 | b = softwareSystem "B" 17 | c = softwareSystem "C" 18 | 19 | a -> b 20 | b -> c 21 | } 22 | 23 | views { 24 | systemLandscape { 25 | include * 26 | autolayout lr 27 | } 28 | 29 | styles { 30 | relationship "Relationship" { 31 | color #ff0000 32 | dashed false 33 | } 34 | } 35 | } 36 | 37 | } 38 | ``` 39 | 40 | [![](example-2.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/relationship-styles/example-2.dsl) 41 | 42 | ## Styling individual relationships 43 | 44 | To change the style of an individual relationship: 45 | 46 | 1. Tag the relationship. 47 | 2. Add a relationship style for that tag. 48 | 49 | ``` 50 | workspace { 51 | 52 | model { 53 | a = softwareSystem "A" 54 | b = softwareSystem "B" 55 | c = softwareSystem "C" 56 | 57 | a -> b 58 | b -> c { 59 | tags "Tag 1" 60 | } 61 | } 62 | 63 | views { 64 | systemLandscape { 65 | include * 66 | autolayout lr 67 | } 68 | 69 | styles { 70 | relationship "Tag 1" { 71 | color #ff0000 72 | dashed false 73 | } 74 | } 75 | } 76 | 77 | } 78 | ``` 79 | 80 | [![](example-3.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/relationship-styles/example-3.dsl) 81 | 82 | ## Notes 83 | 84 | Please note that relationship styles are designed to work with the Structurizr cloud service/on-premises installation, and may not be fully supported by the PlantUML, Mermaid, etc export formats. 85 | 86 | - [DSL language reference - styles - relationship](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#relationship-style) -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/SystemLandscapeViewParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.view.SystemLandscapeView; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.fail; 11 | 12 | class SystemLandscapeViewParserTests extends AbstractTests { 13 | 14 | private SystemLandscapeViewParser parser = new SystemLandscapeViewParser(); 15 | 16 | @Test 17 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 18 | DslContext context = context(); 19 | try { 20 | parser.parse(context, tokens("systemLandscape", "key", "description", "extra")); 21 | fail(); 22 | } catch (Exception e) { 23 | assertEquals("Too many tokens, expected: systemLandscape [key] [description] {", e.getMessage()); 24 | } 25 | } 26 | 27 | @Test 28 | void test_parse_CreatesASystemLandscapeView() { 29 | DslContext context = context(); 30 | parser.parse(context, tokens("systemLandscape")); 31 | List<SystemLandscapeView> views = new ArrayList<>(context.getWorkspace().getViews().getSystemLandscapeViews()); 32 | 33 | assertEquals(1, views.size()); 34 | assertEquals("SystemLandscape", views.get(0).getKey()); 35 | assertEquals("", views.get(0).getDescription()); 36 | } 37 | 38 | @Test 39 | void test_parse_CreatesASystemLandscapeViewWithAKey() { 40 | DslContext context = context(); 41 | parser.parse(context, tokens("systemLandscape", "key")); 42 | List<SystemLandscapeView> views = new ArrayList<>(context.getWorkspace().getViews().getSystemLandscapeViews()); 43 | 44 | assertEquals(1, views.size()); 45 | assertEquals("key", views.get(0).getKey()); 46 | assertEquals("", views.get(0).getDescription()); 47 | } 48 | 49 | @Test 50 | void test_parse_CreatesASystemLandscapeViewWithAKeyAndDescription() { 51 | DslContext context = context(); 52 | parser.parse(context, tokens("systemLandscape", "key", "description")); 53 | List<SystemLandscapeView> views = new ArrayList<>(context.getWorkspace().getViews().getSystemLandscapeViews()); 54 | 55 | assertEquals(1, views.size()); 56 | assertEquals("key", views.get(0).getKey()); 57 | assertEquals("description", views.get(0).getDescription()); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ComponentViewParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.model.Container; 5 | import com.structurizr.model.Element; 6 | import com.structurizr.view.ComponentView; 7 | 8 | final class ComponentViewParser extends AbstractParser { 9 | 10 | private static final String GRAMMAR = "component <container identifier> [key] [description] {"; 11 | 12 | private static final String VIEW_TYPE = "Component"; 13 | 14 | private static final int CONTAINER_IDENTIFIER_INDEX = 1; 15 | private static final int KEY_INDEX = 2; 16 | private static final int DESCRIPTION_INDEX = 3; 17 | 18 | ComponentView parse(DslContext context, Tokens tokens) { 19 | // component <container identifier> [key] [description] { 20 | 21 | if (tokens.hasMoreThan(DESCRIPTION_INDEX)) { 22 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 23 | } 24 | 25 | if (!tokens.includes(CONTAINER_IDENTIFIER_INDEX)) { 26 | throw new RuntimeException("Expected: " + GRAMMAR); 27 | } 28 | 29 | Workspace workspace = context.getWorkspace(); 30 | Container container; 31 | String key = ""; 32 | String description = ""; 33 | 34 | String containerIdentifier = tokens.get(CONTAINER_IDENTIFIER_INDEX); 35 | Element element = context.getElement(containerIdentifier); 36 | if (element == null) { 37 | throw new RuntimeException("The container \"" + containerIdentifier + "\" does not exist"); 38 | } 39 | if (element instanceof Container) { 40 | container = (Container)element; 41 | } else { 42 | throw new RuntimeException("The element \"" + containerIdentifier + "\" is not a container"); 43 | } 44 | 45 | if (tokens.includes(KEY_INDEX)) { 46 | key = tokens.get(KEY_INDEX); 47 | } else { 48 | key = removeNonWordCharacters(container.getSoftwareSystem().getName()) + "-" + removeNonWordCharacters(container.getName()) + "-" + VIEW_TYPE; 49 | } 50 | validateViewKey(key); 51 | 52 | if (tokens.includes(DESCRIPTION_INDEX)) { 53 | description = tokens.get(DESCRIPTION_INDEX); 54 | } 55 | 56 | ComponentView view = workspace.getViews().createComponentView(container, key, description); 57 | view.setExternalSoftwareSystemBoundariesVisible(true); 58 | 59 | return view; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentViewAnimationStepParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.ContainerInstance; 4 | import com.structurizr.model.Element; 5 | import com.structurizr.model.InfrastructureNode; 6 | import com.structurizr.model.StaticStructureElementInstance; 7 | import com.structurizr.view.DeploymentView; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | final class DeploymentViewAnimationStepParser extends AbstractParser { 13 | 14 | void parse(DeploymentViewDslContext context, Tokens tokens) { 15 | // animationStep <identifier> [identifier...] 16 | 17 | if (!tokens.includes(1)) { 18 | throw new RuntimeException("Expected: animationStep <identifier> [identifier...]"); 19 | } 20 | 21 | parse(context, context.getView(), tokens, 1); 22 | } 23 | 24 | void parse(DeploymentViewAnimationDslContext context, Tokens tokens) { 25 | // animationStep <identifier> [identifier...] 26 | 27 | if (!tokens.includes(0)) { 28 | throw new RuntimeException("Expected: <identifier> [identifier...]"); 29 | } 30 | 31 | parse(context, context.getView(), tokens, 0); 32 | } 33 | 34 | void parse(DslContext context, DeploymentView view, Tokens tokens, int startIndex) { 35 | List<StaticStructureElementInstance> staticStructureElementInstances = new ArrayList<>(); 36 | List<InfrastructureNode> infrastructureNodes = new ArrayList<>(); 37 | 38 | for (int i = startIndex; i < tokens.size(); i++) { 39 | String identifier = tokens.get(i); 40 | 41 | Element element = context.getElement(identifier); 42 | if (element == null) { 43 | throw new RuntimeException("The element \"" + identifier + "\" does not exist"); 44 | } 45 | 46 | if (element instanceof StaticStructureElementInstance) { 47 | staticStructureElementInstances.add((StaticStructureElementInstance)element); 48 | } 49 | 50 | if (element instanceof InfrastructureNode) { 51 | infrastructureNodes.add((InfrastructureNode)element); 52 | } 53 | } 54 | 55 | if (!(staticStructureElementInstances.isEmpty() && infrastructureNodes.isEmpty())) { 56 | view.addAnimation(staticStructureElementInstances.toArray(new StaticStructureElementInstance[0]), infrastructureNodes.toArray(new InfrastructureNode[0])); 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /docs/cookbook/amazon-web-services/example-3.dsl: -------------------------------------------------------------------------------- 1 | workspace { 2 | 3 | model { 4 | u = person "User" 5 | s = softwareSystem "Software System" { 6 | webapp = container "Web Application" "" "Spring Boot" 7 | database = container "Database" "" "Relational database schema" 8 | } 9 | 10 | u -> webapp "Uses" 11 | webapp -> database "Reads from and writes to" 12 | 13 | live = deploymentEnvironment "Live" { 14 | deploymentNode "Amazon Web Services" { 15 | tags "Amazon Web Services - Cloud" 16 | 17 | deploymentNode "US-East-1" { 18 | tags "Amazon Web Services - Region" 19 | 20 | route53 = infrastructureNode "Route 53" { 21 | tags "Amazon Web Services - Route 53" 22 | } 23 | elb = infrastructureNode "Elastic Load Balancer" { 24 | tags "Amazon Web Services - Elastic Load Balancing" 25 | } 26 | 27 | deploymentNode "Amazon EC2" { 28 | tags "Amazon Web Services - EC2" 29 | 30 | deploymentNode "Ubuntu Server" { 31 | webApplicationInstance = containerInstance webapp 32 | } 33 | } 34 | 35 | deploymentNode "Amazon RDS" { 36 | tags "Amazon Web Services - RDS" 37 | 38 | deploymentNode "MySQL" { 39 | tags "Amazon Web Services - RDS MySQL instance" 40 | 41 | containerInstance database 42 | } 43 | } 44 | } 45 | } 46 | 47 | route53 -> elb "Forwards requests to" "HTTPS" 48 | elb -> webApplicationInstance "Forwards requests to" "HTTPS" 49 | } 50 | } 51 | 52 | views { 53 | deployment s live { 54 | include * 55 | autoLayout lr 56 | } 57 | 58 | styles { 59 | element "Element" { 60 | shape RoundedBox 61 | background #ffffff 62 | color #000000 63 | } 64 | } 65 | 66 | theme https://static.structurizr.com/themes/amazon-web-services-2020.04.30/theme.json 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/BrandingParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.util.ImageUtils; 4 | import com.structurizr.view.Font; 5 | 6 | import java.io.File; 7 | 8 | final class BrandingParser extends AbstractParser { 9 | 10 | private static final String LOGO_GRAMMAR = "logo <path>"; 11 | private static final String FONT_GRAMMAR = "font <name> [url]"; 12 | 13 | private static final int LOGO_FILE_INDEX = 1; 14 | 15 | private static final int FONT_NAME_INDEX = 1; 16 | private static final int FONT_URL_INDEX = 2; 17 | 18 | void parseLogo(BrandingDslContext context, Tokens tokens) { 19 | // logo <path> 20 | 21 | if (tokens.hasMoreThan(LOGO_FILE_INDEX)) { 22 | throw new RuntimeException("Too many tokens, expected: " + LOGO_GRAMMAR); 23 | } else if (tokens.includes(LOGO_FILE_INDEX)) { 24 | String path = tokens.get(1); 25 | 26 | File file = new File(context.getFile().getParent(), path); 27 | if (file.exists() && !file.isDirectory()) { 28 | try { 29 | String dataUri = ImageUtils.getImageAsDataUri(file); 30 | context.getWorkspace().getViews().getConfiguration().getBranding().setLogo(dataUri); 31 | } catch (Exception e) { 32 | throw new RuntimeException(e); 33 | } 34 | } else { 35 | throw new RuntimeException(path + " does not exist"); 36 | } 37 | } else { 38 | throw new RuntimeException("Expected: " + LOGO_GRAMMAR); 39 | } 40 | } 41 | 42 | void parseFont(BrandingDslContext context, Tokens tokens) { 43 | // font <name> [url] 44 | 45 | if (tokens.hasMoreThan(FONT_URL_INDEX)) { 46 | throw new RuntimeException("Too many tokens, expected: " + FONT_GRAMMAR); 47 | } else if (tokens.includes(FONT_URL_INDEX)) { 48 | String name = tokens.get(FONT_NAME_INDEX); 49 | String url = tokens.get(FONT_URL_INDEX); 50 | 51 | context.getWorkspace().getViews().getConfiguration().getBranding().setFont(new Font(name, url)); 52 | } else if (tokens.includes(FONT_NAME_INDEX)) { 53 | String name = tokens.get(FONT_NAME_INDEX); 54 | 55 | context.getWorkspace().getViews().getConfiguration().getBranding().setFont(new Font(name)); 56 | } else { 57 | throw new RuntimeException("Expected: " + FONT_GRAMMAR); 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SoftwareSystemInstanceParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.*; 4 | 5 | final class SoftwareSystemInstanceParser extends AbstractParser { 6 | 7 | private static final String GRAMMAR = "softwareSystemInstance <identifier> [deploymentGroup|tags] [tags]"; 8 | 9 | private static final int IDENTIFIER_INDEX = 1; 10 | private static final int SECOND_TOKEN = 2; 11 | private static final int THIRD_TOKEN = 3; 12 | 13 | SoftwareSystemInstance parse(DeploymentNodeDslContext context, Tokens tokens) { 14 | // softwareSystemInstance <identifier> [tags] 15 | // softwareSystemInstance <identifier> [deploymentGroup] [tags] 16 | 17 | if (tokens.hasMoreThan(THIRD_TOKEN)) { 18 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 19 | } 20 | 21 | if (!tokens.includes(IDENTIFIER_INDEX)) { 22 | throw new RuntimeException("Expected: " + GRAMMAR); 23 | } 24 | 25 | String softwareSystemIdentifier = tokens.get(IDENTIFIER_INDEX); 26 | 27 | Element element = context.getElement(softwareSystemIdentifier); 28 | if (element == null) { 29 | throw new RuntimeException("The software system \"" + softwareSystemIdentifier + "\" does not exist"); 30 | } 31 | 32 | if (element instanceof SoftwareSystem) { 33 | DeploymentNode deploymentNode = context.getDeploymentNode(); 34 | 35 | String deploymentGroup = DeploymentElement.DEFAULT_DEPLOYMENT_GROUP; 36 | int tagsIndex = SECOND_TOKEN; 37 | 38 | if (tokens.includes(SECOND_TOKEN)) { 39 | String token = tokens.get(SECOND_TOKEN); 40 | 41 | if (context.getElement(token) instanceof DeploymentGroup) { 42 | deploymentGroup = context.getElement(token).getName(); 43 | tagsIndex = THIRD_TOKEN; 44 | } 45 | } 46 | 47 | SoftwareSystemInstance softwareSystemInstance = deploymentNode.add((SoftwareSystem)element, deploymentGroup); 48 | 49 | if (tokens.includes(tagsIndex)) { 50 | String tags = tokens.get(tagsIndex); 51 | softwareSystemInstance.addTags(tags.split(",")); 52 | } 53 | 54 | return softwareSystemInstance; 55 | } else { 56 | throw new RuntimeException("The element \"" + softwareSystemIdentifier + "\" is not a software system"); 57 | } 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /docs/cookbook/dynamic-view/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic view 2 | 3 | A [dynamic view](https://c4model.com/#DynamicDiagram) allows you to show a subset of the elements in a model, and the relationships between them, in order to describe how a particular use case/story/feature works. Dynamic views show ordered __instances__ of relationships, therefore reducing the number of relationships you need to define in the static model - see [Modelling multiple relationships](https://dev.to/simonbrown/modelling-multiple-relationships-51bf) for more. 4 | 5 | ``` 6 | workspace { 7 | 8 | model { 9 | customer = person "Customer" 10 | onlineBookStore = softwareSystem "Online book store" { 11 | webapp = container "Web Application" 12 | database = container "Database" 13 | } 14 | 15 | customer -> webapp "Browses and makes purchases using" 16 | webapp -> database "Reads from and writes to" 17 | } 18 | 19 | views { 20 | container onlineBookStore { 21 | include * 22 | autoLayout lr 23 | } 24 | 25 | dynamic onlineBookStore { 26 | title "Request past orders feature" 27 | customer -> webapp "Requests past orders from" 28 | webapp -> database "Queries for orders using" 29 | autoLayout lr 30 | } 31 | 32 | dynamic onlineBookStore { 33 | title "Browse top 20 books feature" 34 | customer -> webapp "Requests the top 20 books from" 35 | webapp -> database "Queries the top 20 books using" 36 | autoLayout lr 37 | } 38 | } 39 | 40 | } 41 | ``` 42 | 43 | This DSL defines three views: 44 | 45 | 1. A container view showing the user and containers - notice how the relationship between the user and the web application is quite general ("Browses and makes purchases using"). 46 | 2. A dynamic view for the "request past orders" feature. 47 | 2. A dynamic view for the "browse top 20 books" feature. 48 | 49 | [![](example-1.png)](http://structurizr.com/dsl?src=https://raw.githubusercontent.com/structurizr/dsl/master/docs/cookbook/dynamic-view/example-1.dsl) 50 | 51 | Dynamic views can be rendered using the Structurizr cloud service/on-premises installation or exported to a number of other formats via the [Structurizr CLI export command](https://github.com/structurizr/cli/blob/master/docs/export.md). 52 | 53 | ## Links 54 | 55 | - [DSL language reference - dynamic](https://github.com/structurizr/dsl/blob/master/docs/language-reference.md#dynamic-view) -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ContainerViewParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.model.Element; 5 | import com.structurizr.model.SoftwareSystem; 6 | import com.structurizr.view.ContainerView; 7 | 8 | final class ContainerViewParser extends AbstractParser { 9 | 10 | private static final String GRAMMAR = "container <software system identifier> [key] [description] {"; 11 | 12 | private static final String VIEW_TYPE = "Container"; 13 | 14 | private static final int SOFTWARE_SYSTEM_IDENTIFIER_INDEX = 1; 15 | private static final int KEY_INDEX = 2; 16 | private static final int DESCRIPTION_INDEX = 3; 17 | 18 | ContainerView parse(DslContext context, Tokens tokens) { 19 | // container <software system identifier> [key] [description] { 20 | 21 | if (tokens.hasMoreThan(DESCRIPTION_INDEX)) { 22 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 23 | } 24 | 25 | if (!tokens.includes(SOFTWARE_SYSTEM_IDENTIFIER_INDEX)) { 26 | throw new RuntimeException("Expected: " + GRAMMAR); 27 | } 28 | 29 | Workspace workspace = context.getWorkspace(); 30 | SoftwareSystem softwareSystem; 31 | String key = ""; 32 | String description = ""; 33 | 34 | String softwareSystemIdentifier = tokens.get(SOFTWARE_SYSTEM_IDENTIFIER_INDEX); 35 | Element element = context.getElement(softwareSystemIdentifier); 36 | if (element == null) { 37 | throw new RuntimeException("The software system \"" + softwareSystemIdentifier + "\" does not exist"); 38 | } 39 | if (element instanceof SoftwareSystem) { 40 | softwareSystem = (SoftwareSystem)element; 41 | } else { 42 | throw new RuntimeException("The element \"" + softwareSystemIdentifier + "\" is not a software system"); 43 | } 44 | 45 | if (tokens.includes(KEY_INDEX)) { 46 | key = tokens.get(KEY_INDEX); 47 | } else { 48 | key = key = removeNonWordCharacters(softwareSystem.getName()) + "-" + VIEW_TYPE; 49 | } 50 | validateViewKey(key); 51 | 52 | if (tokens.includes(DESCRIPTION_INDEX)) { 53 | description = tokens.get(DESCRIPTION_INDEX); 54 | } 55 | 56 | ContainerView view = workspace.getViews().createContainerView(softwareSystem, key, description); 57 | view.setExternalSoftwareSystemBoundariesVisible(true); 58 | 59 | return view; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/SystemContextViewParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.model.Element; 5 | import com.structurizr.model.SoftwareSystem; 6 | import com.structurizr.view.SystemContextView; 7 | 8 | final class SystemContextViewParser extends AbstractParser { 9 | 10 | private static final String GRAMMAR = "systemContext <software system identifier> [key] [description] {"; 11 | 12 | private static final String VIEW_TYPE = "SystemContext"; 13 | 14 | private static final int SOFTWARE_SYSTEM_IDENTIFIER_INDEX = 1; 15 | private static final int KEY_INDEX = 2; 16 | private static final int DESCRIPTION_INDEX = 3; 17 | 18 | SystemContextView parse(DslContext context, Tokens tokens) { 19 | // systemContext <software system identifier> [key] [description] { 20 | 21 | Workspace workspace = context.getWorkspace(); 22 | SoftwareSystem softwareSystem; 23 | String key = ""; 24 | String description = ""; 25 | 26 | if (tokens.hasMoreThan(DESCRIPTION_INDEX)) { 27 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 28 | } 29 | 30 | if (!tokens.includes(SOFTWARE_SYSTEM_IDENTIFIER_INDEX)) { 31 | throw new RuntimeException("Expected: " + GRAMMAR); 32 | } 33 | 34 | String softwareSystemIdentifier = tokens.get(SOFTWARE_SYSTEM_IDENTIFIER_INDEX); 35 | Element element = context.getElement(softwareSystemIdentifier); 36 | if (element == null) { 37 | throw new RuntimeException("The software system \"" + softwareSystemIdentifier + "\" does not exist"); 38 | } 39 | if (element instanceof SoftwareSystem) { 40 | softwareSystem = (SoftwareSystem)element; 41 | } else { 42 | throw new RuntimeException("The element \"" + softwareSystemIdentifier + "\" is not a software system"); 43 | } 44 | 45 | if (tokens.includes(KEY_INDEX)) { 46 | key = tokens.get(KEY_INDEX); 47 | } else { 48 | key = removeNonWordCharacters(softwareSystem.getName()) + "-" + VIEW_TYPE; 49 | } 50 | validateViewKey(key); 51 | 52 | if (tokens.includes(DESCRIPTION_INDEX)) { 53 | description = tokens.get(DESCRIPTION_INDEX); 54 | } 55 | 56 | SystemContextView view = workspace.getViews().createSystemContextView(softwareSystem, key, description); 57 | view.setEnterpriseBoundaryVisible(true); 58 | 59 | return view; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/HealthCheckParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.StaticStructureElementInstance; 4 | 5 | class HealthCheckParser extends AbstractParser { 6 | 7 | private static final String GRAMMAR = "healthCheck <name> <url> [interval] [timeout]"; 8 | 9 | private final static int NAME_INDEX = 1; 10 | private final static int URL_INDEX = 2; 11 | private final static int INTERVAL_INDEX = 3; 12 | private final static int TIMEOUT_INDEX = 4; 13 | 14 | private final static int DEFAULT_INTERVAL = 60; 15 | private final static long DEFAULT_TIMEOUT = 0; 16 | 17 | void parse(StaticStructureElementInstanceDslContext context, Tokens tokens) { 18 | // healthCheck <name> <url> [interval] [timeout] 19 | 20 | if (tokens.hasMoreThan(TIMEOUT_INDEX)) { 21 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 22 | } 23 | 24 | if (!tokens.includes(URL_INDEX)) { 25 | throw new RuntimeException("Expected: " + GRAMMAR); 26 | } 27 | 28 | String name = tokens.get(NAME_INDEX); 29 | String url = tokens.get(URL_INDEX); 30 | int interval = DEFAULT_INTERVAL; 31 | long timeout = DEFAULT_TIMEOUT; 32 | 33 | if (tokens.includes(INTERVAL_INDEX)) { 34 | try { 35 | interval = Integer.parseInt(tokens.get(INTERVAL_INDEX)); 36 | 37 | if (interval < 1) { 38 | throw new RuntimeException("The interval must be a positive integer (number of seconds)"); 39 | } 40 | } catch (NumberFormatException e) { 41 | throw new RuntimeException("The interval of \"" + tokens.get(INTERVAL_INDEX) + "\" is not valid - it must be a positive integer (number of seconds)"); 42 | } 43 | } 44 | 45 | if (tokens.includes(TIMEOUT_INDEX)) { 46 | try { 47 | timeout = Integer.parseInt(tokens.get(TIMEOUT_INDEX)); 48 | 49 | if (timeout < 0) { 50 | throw new RuntimeException("The timeout must be zero or a positive integer (number of milliseconds)"); 51 | } 52 | } catch (NumberFormatException e) { 53 | throw new RuntimeException("The timeout of \"" + tokens.get(TIMEOUT_INDEX) + "\" is not valid - it must be zero or a positive integer (number of milliseconds)"); 54 | } 55 | } 56 | 57 | StaticStructureElementInstance elementInstance = context.getElementInstance(); 58 | elementInstance.addHealthCheck(name, url, interval, timeout); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ImpliedRelationshipsParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy; 4 | import com.structurizr.model.DefaultImpliedRelationshipsStrategy; 5 | import com.structurizr.model.ImpliedRelationshipsStrategy; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | final class ImpliedRelationshipsParser extends AbstractParser { 11 | 12 | private static final String GRAMMAR_RESTRICTED = "!impliedRelationships <true|false>"; 13 | private static final String GRAMMAR_UNRESTRICTED = "!impliedRelationships <true|false|fqn>"; 14 | 15 | private static final int FLAG_INDEX = 1; 16 | private static final String FALSE = "false"; 17 | private static final String TRUE = "true"; 18 | 19 | private boolean restricted = false; 20 | 21 | ImpliedRelationshipsParser(boolean restricted) { 22 | this.restricted = restricted; 23 | } 24 | 25 | void parse(DslContext context, Tokens tokens) { 26 | // impliedRelationships <true|false|fqn> 27 | 28 | if (tokens.hasMoreThan(FLAG_INDEX)) { 29 | throw new RuntimeException("Too many tokens, expected: " + (restricted ? GRAMMAR_RESTRICTED : GRAMMAR_UNRESTRICTED)); 30 | } 31 | 32 | if (!tokens.includes(FLAG_INDEX)) { 33 | throw new RuntimeException("Expected: " + (restricted ? GRAMMAR_RESTRICTED : GRAMMAR_UNRESTRICTED)); 34 | } 35 | 36 | if (tokens.get(FLAG_INDEX).equalsIgnoreCase(FALSE)) { 37 | context.getWorkspace().getModel().setImpliedRelationshipsStrategy(new DefaultImpliedRelationshipsStrategy()); 38 | } else if (tokens.get(FLAG_INDEX).equalsIgnoreCase(TRUE)) { 39 | context.getWorkspace().getModel().setImpliedRelationshipsStrategy(new CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy()); 40 | } else { 41 | if (restricted) { 42 | throw new RuntimeException("Custom implied relationship strategies are not available when running in restricted mode, expected: " + GRAMMAR_RESTRICTED); 43 | } else { 44 | String fqn = tokens.get(FLAG_INDEX); 45 | try { 46 | ImpliedRelationshipsStrategy strategy = (ImpliedRelationshipsStrategy)Class.forName(fqn).getDeclaredConstructor().newInstance(); 47 | context.getWorkspace().getModel().setImpliedRelationshipsStrategy(strategy); 48 | } catch (Exception e) { 49 | throw new RuntimeException("Could not load implied relationships strategy " + fqn); 50 | } 51 | } 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/ModelItemParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | final class ModelItemParser extends AbstractParser { 4 | 5 | private final static int TAGS_INDEX = 1; 6 | 7 | private final static int URL_INDEX = 1; 8 | 9 | private final static int PROPERTY_NAME_INDEX = 0; 10 | private final static int PROPERTY_VALUE_INDEX = 1; 11 | 12 | private final static int PERSPECTIVE_NAME_INDEX = 0; 13 | private final static int PERSPECTIVE_DESCRIPTION_INDEX = 1; 14 | 15 | void parseTags(ModelItemDslContext context, Tokens tokens) { 16 | // tags <tags> [tags] 17 | if (!tokens.includes(TAGS_INDEX)) { 18 | throw new RuntimeException("Expected: tags <tags> [tags]"); 19 | } 20 | 21 | for (int i = TAGS_INDEX; i < tokens.size(); i++) { 22 | String tags = tokens.get(i); 23 | context.getModelItem().addTags(tags.split(",")); 24 | } 25 | } 26 | 27 | void parseUrl(ModelItemDslContext context, Tokens tokens) { 28 | // url <url> 29 | if (tokens.hasMoreThan(URL_INDEX)) { 30 | throw new RuntimeException("Too many tokens, expected: url <url>"); 31 | } 32 | 33 | if (!tokens.includes(URL_INDEX)) { 34 | throw new RuntimeException("Expected: url <url>"); 35 | } 36 | 37 | String url = tokens.get(URL_INDEX); 38 | context.getModelItem().setUrl(url); 39 | } 40 | 41 | void parseProperty(ModelItemPropertiesDslContext context, Tokens tokens) { 42 | // <name> <value> 43 | 44 | if (tokens.hasMoreThan(PROPERTY_VALUE_INDEX)) { 45 | throw new RuntimeException("Too many tokens, expected: <name> <value>"); 46 | } 47 | 48 | if (tokens.size() != 2) { 49 | throw new RuntimeException("Expected: <name> <value>"); 50 | } 51 | 52 | String name = tokens.get(PROPERTY_NAME_INDEX); 53 | String value = tokens.get(PROPERTY_VALUE_INDEX); 54 | 55 | context.getModelItem().addProperty(name, value); 56 | } 57 | 58 | void parsePerspective(ModelItemPerspectivesDslContext context, Tokens tokens) { 59 | // <name> <description> 60 | 61 | if (tokens.hasMoreThan(PERSPECTIVE_DESCRIPTION_INDEX)) { 62 | throw new RuntimeException("Too many tokens, expected: <name> <description>"); 63 | } 64 | 65 | if (tokens.size() != 2) { 66 | throw new RuntimeException("Expected: <name> <description>"); 67 | } 68 | 69 | String name = tokens.get(PERSPECTIVE_NAME_INDEX); 70 | String value = tokens.get(PERSPECTIVE_DESCRIPTION_INDEX); 71 | 72 | context.getModelItem().addPerspective(name, value); 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /gradle/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/test/java/com/structurizr/dsl/UserRoleParserTests.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.configuration.Role; 4 | import com.structurizr.configuration.User; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Set; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.fail; 11 | 12 | class UserRoleParserTests extends AbstractTests { 13 | 14 | private UserRoleParser parser = new UserRoleParser(); 15 | 16 | @Test 17 | void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { 18 | try { 19 | parser.parse(context(), tokens("username", "role", "extra")); 20 | fail(); 21 | } catch (Exception e) { 22 | assertEquals("Too many tokens, expected: <username> <read|write>", e.getMessage()); 23 | } 24 | } 25 | 26 | @Test 27 | void test_parse_ThrowsAnException_WhenNoUsernameIsSpecified() { 28 | try { 29 | parser.parse(context(), tokens("")); 30 | fail(); 31 | } catch (Exception e) { 32 | assertEquals("Expected: <username> <read|write>", e.getMessage()); 33 | } 34 | } 35 | 36 | @Test 37 | void test_parse_ThrowsAnException_WhenNoRoleIsSpecified() { 38 | try { 39 | parser.parse(context(), tokens("user@example.com")); 40 | fail(); 41 | } catch (Exception e) { 42 | assertEquals("Expected: <username> <read|write>", e.getMessage()); 43 | } 44 | } 45 | 46 | @Test 47 | void test_parse_ThrowsAnException_WhenAnInvalidRoleIsSpecified() { 48 | try { 49 | parser.parse(context(), tokens("user@example.com", "role")); 50 | fail(); 51 | } catch (Exception e) { 52 | assertEquals("The role should be \"read\" or \"write\"", e.getMessage()); 53 | } 54 | } 55 | 56 | @Test 57 | void test_parse_AddsAReadOnlyUser() { 58 | parser.parse(context(), tokens("user@example.com", "read")); 59 | 60 | Set<User> users = context().getWorkspace().getConfiguration().getUsers(); 61 | assertEquals(1, users.size()); 62 | 63 | User user = users.stream().findFirst().get(); 64 | assertEquals("user@example.com", user.getUsername()); 65 | assertEquals(Role.ReadOnly, user.getRole()); 66 | } 67 | 68 | @Test 69 | void test_parse_AddsAReadWriteUser() { 70 | parser.parse(context(), tokens("user@example.com", "write")); 71 | 72 | Set<User> users = context().getWorkspace().getConfiguration().getUsers(); 73 | assertEquals(1, users.size()); 74 | 75 | User user = users.stream().findFirst().get(); 76 | assertEquals("user@example.com", user.getUsername()); 77 | assertEquals(Role.ReadWrite, user.getRole()); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DeploymentNodeParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.DeploymentNode; 4 | 5 | final class DeploymentNodeParser extends AbstractParser { 6 | 7 | private static final String GRAMMAR = "deploymentNode <name> [description] [technology] [tags] [instances] {"; 8 | 9 | private static final int NAME_INDEX = 1; 10 | private static final int DESCRIPTION_INDEX = 2; 11 | private static final int TECHNOLOGY_INDEX = 3; 12 | private static final int TAGS_INDEX = 4; 13 | private static final int INSTANCES_INDEX = 5; 14 | 15 | DeploymentNode parse(DslContext context, Tokens tokens) { 16 | // deploymentNode <name> [description] [technology] [tags] [instances] 17 | 18 | if (tokens.hasMoreThan(INSTANCES_INDEX)) { 19 | throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); 20 | } 21 | 22 | if (!tokens.includes(NAME_INDEX)) { 23 | throw new RuntimeException("Expected: " + GRAMMAR); 24 | } 25 | 26 | DeploymentNode deploymentNode = null; 27 | String name = tokens.get(NAME_INDEX); 28 | 29 | String description = ""; 30 | if (tokens.includes(DESCRIPTION_INDEX)) { 31 | description = tokens.get(DESCRIPTION_INDEX); 32 | } 33 | 34 | String technology = ""; 35 | if (tokens.includes(TECHNOLOGY_INDEX)) { 36 | technology = tokens.get(TECHNOLOGY_INDEX); 37 | } 38 | 39 | if (context instanceof DeploymentEnvironmentDslContext) { 40 | deploymentNode = context.getWorkspace().getModel().addDeploymentNode(((DeploymentEnvironmentDslContext)context).getEnvironment(), name, description, technology); 41 | } else if (context instanceof DeploymentNodeDslContext) { 42 | DeploymentNode parent = ((DeploymentNodeDslContext)context).getDeploymentNode(); 43 | deploymentNode = parent.addDeploymentNode(name, description, technology); 44 | } else { 45 | throw new RuntimeException("Unexpected deployment node"); 46 | } 47 | 48 | String tags = ""; 49 | if (tokens.includes(TAGS_INDEX)) { 50 | tags = tokens.get(TAGS_INDEX); 51 | deploymentNode.addTags(tags.split(",")); 52 | } 53 | 54 | int instances = 1; 55 | if (tokens.includes(INSTANCES_INDEX)) { 56 | String instancesAsString = tokens.get(INSTANCES_INDEX); 57 | try { 58 | instances = Integer.parseInt(instancesAsString); 59 | } catch (NumberFormatException e) { 60 | throw new RuntimeException("\"" + instancesAsString + "\" is not a valid number of instances"); 61 | } 62 | deploymentNode.setInstances(instances); 63 | } 64 | 65 | return deploymentNode; 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/StaticViewExpressionParser.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.model.*; 4 | 5 | import java.util.LinkedHashSet; 6 | import java.util.Set; 7 | 8 | import static com.structurizr.dsl.StructurizrDslExpressions.ELEMENT_TYPE_EQUALS_EXPRESSION; 9 | 10 | final class StaticViewExpressionParser extends AbstractExpressionParser { 11 | 12 | @Override 13 | protected Set<Element> evaluateElementTypeExpression(String expr, DslContext context) { 14 | Set<Element> elements = new LinkedHashSet<>(); 15 | 16 | String type = expr.substring(ELEMENT_TYPE_EQUALS_EXPRESSION.length()); 17 | switch (type.toLowerCase()) { 18 | case "custom": 19 | context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof CustomElement).forEach(elements::add); 20 | break; 21 | case "person": 22 | context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Person).forEach(elements::add); 23 | break; 24 | case "softwaresystem": 25 | context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof SoftwareSystem).forEach(elements::add); 26 | break; 27 | case "container": 28 | context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Container).forEach(elements::add); 29 | break; 30 | case "component": 31 | context.getWorkspace().getModel().getElements().stream().filter(e -> e instanceof Component).forEach(elements::add); 32 | break; 33 | default: 34 | throw new RuntimeException("The element type of \"" + type + "\" is not valid for this view"); 35 | } 36 | 37 | return elements; 38 | } 39 | 40 | protected Set<Element> findAfferentCouplings(Element element) { 41 | Set<Element> elements = new LinkedHashSet<>(); 42 | 43 | elements.addAll(findAfferentCouplings(element, Person.class)); 44 | elements.addAll(findAfferentCouplings(element, SoftwareSystem.class)); 45 | elements.addAll(findAfferentCouplings(element, Container.class)); 46 | elements.addAll(findAfferentCouplings(element, Component.class)); 47 | 48 | return elements; 49 | } 50 | 51 | protected Set<Element> findEfferentCouplings(Element element) { 52 | Set<Element> elements = new LinkedHashSet<>(); 53 | 54 | elements.addAll(findEfferentCouplings(element, Person.class)); 55 | elements.addAll(findEfferentCouplings(element, SoftwareSystem.class)); 56 | elements.addAll(findEfferentCouplings(element, Container.class)); 57 | elements.addAll(findEfferentCouplings(element, Component.class)); 58 | 59 | return elements; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /src/main/java/com/structurizr/dsl/DslContext.java: -------------------------------------------------------------------------------- 1 | package com.structurizr.dsl; 2 | 3 | import com.structurizr.Workspace; 4 | import com.structurizr.model.Element; 5 | import com.structurizr.model.Relationship; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | abstract class DslContext { 11 | 12 | static final String CONTEXT_START_TOKEN = "{"; 13 | static final String CONTEXT_END_TOKEN = "}"; 14 | 15 | private Workspace workspace; 16 | private boolean extendingWorkspace; 17 | 18 | protected IdentifersRegister identifersRegister = new IdentifersRegister(); 19 | 20 | Workspace getWorkspace() { 21 | return workspace; 22 | } 23 | 24 | void setWorkspace(Workspace workspace) { 25 | this.workspace = workspace; 26 | } 27 | 28 | boolean isExtendingWorkspace() { 29 | return extendingWorkspace; 30 | } 31 | 32 | void setExtendingWorkspace(boolean extendingWorkspace) { 33 | this.extendingWorkspace = extendingWorkspace; 34 | } 35 | 36 | void setIdentifierRegister(IdentifersRegister identifersRegister) { 37 | this.identifersRegister = identifersRegister; 38 | } 39 | 40 | Element getElement(String identifier) { 41 | Element element = identifersRegister.getElement(identifier.toLowerCase()); 42 | 43 | if (element == null && identifersRegister.getIdentifierScope() == IdentifierScope.Hierarchical) { 44 | if (this instanceof ModelItemDslContext) { 45 | ModelItemDslContext modelItemDslContext = (ModelItemDslContext)this; 46 | if (modelItemDslContext.getModelItem() instanceof Element) { 47 | Element parent = (Element)modelItemDslContext.getModelItem(); 48 | while (parent != null && element == null) { 49 | String parentIdentifier = identifersRegister.findIdentifier(parent); 50 | 51 | element = identifersRegister.getElement(parentIdentifier + "." + identifier); 52 | parent = parent.getParent(); 53 | } 54 | } 55 | } else if (this instanceof DeploymentEnvironmentDslContext) { 56 | DeploymentEnvironmentDslContext deploymentEnvironmentDslContext = (DeploymentEnvironmentDslContext)this; 57 | DeploymentEnvironment deploymentEnvironment = new DeploymentEnvironment(deploymentEnvironmentDslContext.getEnvironment()); 58 | String parentIdentifier = identifersRegister.findIdentifier(deploymentEnvironment); 59 | 60 | element = identifersRegister.getElement(parentIdentifier + "." + identifier); 61 | } 62 | } 63 | 64 | return element; 65 | } 66 | 67 | Relationship getRelationship(String identifier) { 68 | return identifersRegister.getRelationship(identifier.toLowerCase()); 69 | } 70 | 71 | void end() { 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /examples/amazon-web-services.dsl: -------------------------------------------------------------------------------- 1 | workspace "Amazon Web Services Example" "An example AWS deployment architecture." { 2 | model { 3 | springPetClinic = softwaresystem "Spring PetClinic" "Allows employees to view and manage information regarding the veterinarians, the clients, and their pets." "Spring Boot Application" { 4 | webApplication = container "Web Application" "Allows employees to view and manage information regarding the veterinarians, the clients, and their pets." "Java and Spring Boot" 5 | database = container "Database" "Stores information regarding the veterinarians, the clients, and their pets." "Relational database schema" "Database" 6 | } 7 | 8 | webApplication -> database "Reads from and writes to" "JDBC/SSL" 9 | 10 | deploymentEnvironment "Live" { 11 | deploymentNode "Amazon Web Services" "" "" "Amazon Web Services - Cloud" { 12 | deploymentNode "US-East-1" "" "" "Amazon Web Services - Region" { 13 | route53 = infrastructureNode "Route 53" "" "" "Amazon Web Services - Route 53" 14 | elb = infrastructureNode "Elastic Load Balancer" "" "" "Amazon Web Services - Elastic Load Balancing" 15 | 16 | deploymentNode "Autoscaling group" "" "" "Amazon Web Services - Auto Scaling" { 17 | deploymentNode "Amazon EC2" "" "" "Amazon Web Services - EC2" { 18 | webApplicationInstance = containerInstance webApplication 19 | } 20 | } 21 | 22 | deploymentNode "Amazon RDS" "" "" "Amazon Web Services - RDS" { 23 | deploymentNode "MySQL" "" "" "Amazon Web Services - RDS MySQL instance" { 24 | databaseInstance = containerInstance database 25 | } 26 | } 27 | 28 | } 29 | } 30 | 31 | route53 -> elb "Forwards requests to" "HTTPS" 32 | elb -> webApplicationInstance "Forwards requests to" "HTTPS" 33 | } 34 | } 35 | 36 | views { 37 | deployment springPetClinic "Live" "AmazonWebServicesDeployment" { 38 | include * 39 | autolayout lr 40 | 41 | animation { 42 | route53 43 | elb 44 | webApplicationInstance 45 | databaseInstance 46 | } 47 | } 48 | 49 | styles { 50 | element "Element" { 51 | shape roundedbox 52 | background #ffffff 53 | } 54 | element "Database" { 55 | shape cylinder 56 | } 57 | element "Infrastructure Node" { 58 | shape roundedbox 59 | } 60 | } 61 | 62 | themes https://static.structurizr.com/themes/amazon-web-services-2020.04.30/theme.json 63 | } 64 | } --------------------------------------------------------------------------------