diff --git a/build.gradle b/build.gradle index 0149cb7..54fa5da 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ repositories { } dependencies { - api 'com.structurizr:structurizr-client:1.23.2' + api 'com.structurizr:structurizr-client:1.24.0' api 'com.structurizr:structurizr-import:1.4.1' testImplementation 'org.codehaus.groovy:groovy-jsr223:3.0.16' diff --git a/docs/changelog.md b/docs/changelog.md index 3a201d4..b01b9dc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,7 @@ - Adds support for splitting lines in the DSL source with a backslash character (https://github.com/structurizr/dsl/issues/137). - Fixes https://github.com/structurizr/dsl/issues/114 (Parallel sequence behavior in dynamic views). - Fixes https://github.com/structurizr/dsl/issues/239 (File context for included files varies based upon how they are included). +- Adds support for grouping deployment nodes, infrastructure nodes, software system instances, and container instances. ## 1.29.1 (17th March 2023) diff --git a/src/main/java/com/structurizr/dsl/ContainerInstanceParser.java b/src/main/java/com/structurizr/dsl/ContainerInstanceParser.java index bbac0a7..c582879 100644 --- a/src/main/java/com/structurizr/dsl/ContainerInstanceParser.java +++ b/src/main/java/com/structurizr/dsl/ContainerInstanceParser.java @@ -53,6 +53,11 @@ ContainerInstance parse(DeploymentNodeDslContext context, Tokens tokens) { containerInstance.addTags(tags.split(",")); } + if (context.hasGroup()) { + containerInstance.setGroup(context.getGroup().getName()); + context.getGroup().addElement(containerInstance); + } + return containerInstance; } diff --git a/src/main/java/com/structurizr/dsl/DeploymentEnvironmentDslContext.java b/src/main/java/com/structurizr/dsl/DeploymentEnvironmentDslContext.java index 9291914..c350272 100644 --- a/src/main/java/com/structurizr/dsl/DeploymentEnvironmentDslContext.java +++ b/src/main/java/com/structurizr/dsl/DeploymentEnvironmentDslContext.java @@ -1,10 +1,16 @@ package com.structurizr.dsl; -final class DeploymentEnvironmentDslContext extends DslContext { +final class DeploymentEnvironmentDslContext extends GroupableDslContext { - private String environment; + private final String environment; DeploymentEnvironmentDslContext(String environment) { + super(null); + this.environment = environment; + } + + DeploymentEnvironmentDslContext(String environment, ElementGroup group) { + super(group); this.environment = environment; } @@ -15,6 +21,7 @@ String getEnvironment() { @Override protected String[] getPermittedTokens() { return new String[] { + StructurizrDslTokens.GROUP_TOKEN, StructurizrDslTokens.DEPLOYMENT_GROUP_TOKEN, StructurizrDslTokens.DEPLOYMENT_NODE_TOKEN, StructurizrDslTokens.RELATIONSHIP_TOKEN diff --git a/src/main/java/com/structurizr/dsl/DeploymentNodeDslContext.java b/src/main/java/com/structurizr/dsl/DeploymentNodeDslContext.java index 1562a60..0cfecda 100644 --- a/src/main/java/com/structurizr/dsl/DeploymentNodeDslContext.java +++ b/src/main/java/com/structurizr/dsl/DeploymentNodeDslContext.java @@ -1,13 +1,20 @@ package com.structurizr.dsl; import com.structurizr.model.DeploymentNode; +import com.structurizr.model.GroupableElement; import com.structurizr.model.ModelItem; -final class DeploymentNodeDslContext extends ModelItemDslContext { +final class DeploymentNodeDslContext extends GroupableElementDslContext { - private DeploymentNode deploymentNode; + private final DeploymentNode deploymentNode; DeploymentNodeDslContext(DeploymentNode deploymentNode) { + this(deploymentNode, null); + } + + public DeploymentNodeDslContext(DeploymentNode deploymentNode, ElementGroup group) { + super(group); + this.deploymentNode = deploymentNode; } @@ -20,9 +27,15 @@ ModelItem getModelItem() { return getDeploymentNode(); } + @Override + GroupableElement getElement() { + return deploymentNode; + } + @Override protected String[] getPermittedTokens() { return new String[] { + StructurizrDslTokens.GROUP_TOKEN, StructurizrDslTokens.DEPLOYMENT_NODE_TOKEN, StructurizrDslTokens.INFRASTRUCTURE_NODE_TOKEN, StructurizrDslTokens.SOFTWARE_SYSTEM_INSTANCE_TOKEN, diff --git a/src/main/java/com/structurizr/dsl/DeploymentNodeParser.java b/src/main/java/com/structurizr/dsl/DeploymentNodeParser.java index 7fdd7b5..ba88d73 100644 --- a/src/main/java/com/structurizr/dsl/DeploymentNodeParser.java +++ b/src/main/java/com/structurizr/dsl/DeploymentNodeParser.java @@ -12,7 +12,7 @@ final class DeploymentNodeParser extends AbstractParser { private static final int TAGS_INDEX = 4; private static final int INSTANCES_INDEX = 5; - DeploymentNode parse(DslContext context, Tokens tokens) { + DeploymentNode parse(DeploymentEnvironmentDslContext context, Tokens tokens) { // deploymentNode [description] [technology] [tags] [instances] if (tokens.hasMoreThan(INSTANCES_INDEX)) { @@ -36,15 +36,55 @@ DeploymentNode parse(DslContext context, Tokens tokens) { technology = tokens.get(TECHNOLOGY_INDEX); } - if (context instanceof DeploymentEnvironmentDslContext) { - deploymentNode = context.getWorkspace().getModel().addDeploymentNode(((DeploymentEnvironmentDslContext)context).getEnvironment(), name, description, technology); - } else if (context instanceof DeploymentNodeDslContext) { - DeploymentNode parent = ((DeploymentNodeDslContext)context).getDeploymentNode(); - deploymentNode = parent.addDeploymentNode(name, description, technology); - } else { - throw new RuntimeException("Unexpected deployment node"); + deploymentNode = context.getWorkspace().getModel().addDeploymentNode(context.getEnvironment(), name, description, technology); + + String tags = ""; + if (tokens.includes(TAGS_INDEX)) { + tags = tokens.get(TAGS_INDEX); + deploymentNode.addTags(tags.split(",")); + } + + String instances = "1"; + if (tokens.includes(INSTANCES_INDEX)) { + instances = tokens.get(INSTANCES_INDEX); + deploymentNode.setInstances(instances); + } + + if (context.hasGroup()) { + deploymentNode.setGroup(context.getGroup().getName()); + context.getGroup().addElement(deploymentNode); + } + + return deploymentNode; + } + + DeploymentNode parse(DeploymentNodeDslContext context, Tokens tokens) { + // deploymentNode [description] [technology] [tags] [instances] + + if (tokens.hasMoreThan(INSTANCES_INDEX)) { + throw new RuntimeException("Too many tokens, expected: " + GRAMMAR); + } + + if (!tokens.includes(NAME_INDEX)) { + throw new RuntimeException("Expected: " + GRAMMAR); } + DeploymentNode deploymentNode = null; + String name = tokens.get(NAME_INDEX); + + String description = ""; + if (tokens.includes(DESCRIPTION_INDEX)) { + description = tokens.get(DESCRIPTION_INDEX); + } + + String technology = ""; + if (tokens.includes(TECHNOLOGY_INDEX)) { + technology = tokens.get(TECHNOLOGY_INDEX); + } + + DeploymentNode parent = context.getDeploymentNode(); + deploymentNode = parent.addDeploymentNode(name, description, technology); + String tags = ""; if (tokens.includes(TAGS_INDEX)) { tags = tokens.get(TAGS_INDEX); @@ -57,6 +97,11 @@ DeploymentNode parse(DslContext context, Tokens tokens) { deploymentNode.setInstances(instances); } + if (context.hasGroup()) { + deploymentNode.setGroup(context.getGroup().getName()); + context.getGroup().addElement(deploymentNode); + } + return deploymentNode; } diff --git a/src/main/java/com/structurizr/dsl/InfrastructureNodeParser.java b/src/main/java/com/structurizr/dsl/InfrastructureNodeParser.java index 7dd7818..9f20ba9 100644 --- a/src/main/java/com/structurizr/dsl/InfrastructureNodeParser.java +++ b/src/main/java/com/structurizr/dsl/InfrastructureNodeParser.java @@ -44,6 +44,11 @@ InfrastructureNode parse(DeploymentNodeDslContext context, Tokens tokens) { infrastructureNode.addTags(tags.split(",")); } + if (context.hasGroup()) { + infrastructureNode.setGroup(context.getGroup().getName()); + context.getGroup().addElement(infrastructureNode); + } + return infrastructureNode; } diff --git a/src/main/java/com/structurizr/dsl/SoftwareSystemInstanceParser.java b/src/main/java/com/structurizr/dsl/SoftwareSystemInstanceParser.java index 60a5dd6..dee362c 100644 --- a/src/main/java/com/structurizr/dsl/SoftwareSystemInstanceParser.java +++ b/src/main/java/com/structurizr/dsl/SoftwareSystemInstanceParser.java @@ -54,6 +54,11 @@ SoftwareSystemInstance parse(DeploymentNodeDslContext context, Tokens tokens) { softwareSystemInstance.addTags(tags.split(",")); } + if (context.hasGroup()) { + softwareSystemInstance.setGroup(context.getGroup().getName()); + context.getGroup().addElement(softwareSystemInstance); + } + return softwareSystemInstance; } diff --git a/src/main/java/com/structurizr/dsl/StructurizrDslParser.java b/src/main/java/com/structurizr/dsl/StructurizrDslParser.java index 6cfb739..603089c 100644 --- a/src/main/java/com/structurizr/dsl/StructurizrDslParser.java +++ b/src/main/java/com/structurizr/dsl/StructurizrDslParser.java @@ -378,6 +378,18 @@ void parse(List lines, File dslFile) throws StructurizrDslParserExceptio group.setParent(container); startContext(new ContainerDslContext(container, group)); registerIdentifier(identifier, group); + } else if (GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentEnvironmentDslContext.class)) { + ElementGroup group = new GroupParser().parse(getContext(DeploymentEnvironmentDslContext.class), tokens.withoutContextStartToken()); + + String environment = getContext(DeploymentEnvironmentDslContext.class).getEnvironment(); + startContext(new DeploymentEnvironmentDslContext(environment, group)); + registerIdentifier(identifier, group); + } else if (GROUP_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) { + ElementGroup group = new GroupParser().parse(getContext(DeploymentNodeDslContext.class), tokens.withoutContextStartToken()); + + DeploymentNode deploymentNode = getContext(DeploymentNodeDslContext.class).getDeploymentNode(); + startContext(new DeploymentNodeDslContext(deploymentNode, group)); + registerIdentifier(identifier, group); } else if (TAGS_TOKEN.equalsIgnoreCase(firstToken) && inContext(ModelItemDslContext.class) && !getContext(ModelItemDslContext.class).hasGroup()) { new ModelItemParser().parseTags(getContext(ModelItemDslContext.class), tokens); @@ -575,8 +587,16 @@ void parse(List lines, File dslFile) throws StructurizrDslParserExceptio registerIdentifier(identifier, new DeploymentGroup(group)); - } else if (DEPLOYMENT_NODE_TOKEN.equalsIgnoreCase(firstToken) && (inContext(DeploymentEnvironmentDslContext.class) || inContext(DeploymentNodeDslContext.class))) { - DeploymentNode deploymentNode = new DeploymentNodeParser().parse(getContext(), tokens.withoutContextStartToken()); + } else if (DEPLOYMENT_NODE_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentEnvironmentDslContext.class)) { + DeploymentNode deploymentNode = new DeploymentNodeParser().parse(getContext(DeploymentEnvironmentDslContext.class), tokens.withoutContextStartToken()); + + if (shouldStartContext(tokens)) { + startContext(new DeploymentNodeDslContext(deploymentNode)); + } + + registerIdentifier(identifier, deploymentNode); + } else if (DEPLOYMENT_NODE_TOKEN.equalsIgnoreCase(firstToken) && inContext(DeploymentNodeDslContext.class)) { + DeploymentNode deploymentNode = new DeploymentNodeParser().parse(getContext(DeploymentNodeDslContext.class), tokens.withoutContextStartToken()); if (shouldStartContext(tokens)) { startContext(new DeploymentNodeDslContext(deploymentNode)); diff --git a/src/test/dsl/groups.dsl b/src/test/dsl/groups.dsl index 1530cb1..d38ce2d 100644 --- a/src/test/dsl/groups.dsl +++ b/src/test/dsl/groups.dsl @@ -17,13 +17,19 @@ workspace { } live = deploymentEnvironment "Live" { - deploymentNode "Server 1" { - containerInstance service1Api - containerInstance service1Database - } - deploymentNode "Server 2" { - containerInstance service2Api - containerInstance service2Database + group "Servers" { + deploymentNode "Server 1" { + group "Service 1" { + containerInstance service1Api + containerInstance service1Database + } + } + deploymentNode "Server 2" { + group "Service 2" { + containerInstance service2Api + containerInstance service2Database + } + } } } diff --git a/src/test/java/com/structurizr/dsl/DeploymentNodeParserTests.java b/src/test/java/com/structurizr/dsl/DeploymentNodeParserTests.java index db03c1e..d9ec9be 100644 --- a/src/test/java/com/structurizr/dsl/DeploymentNodeParserTests.java +++ b/src/test/java/com/structurizr/dsl/DeploymentNodeParserTests.java @@ -13,7 +13,7 @@ class DeploymentNodeParserTests extends AbstractTests { @Test void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { try { - parser.parse(context(), tokens("deploymentNode", "name", "description", "technology", "tags", "instances", "extra")); + parser.parse(new DeploymentEnvironmentDslContext("env"), tokens("deploymentNode", "name", "description", "technology", "tags", "instances", "extra")); fail(); } catch (Exception e) { assertEquals("Too many tokens, expected: deploymentNode [description] [technology] [tags] [instances] {", e.getMessage()); @@ -23,7 +23,7 @@ void test_parse_ThrowsAnException_WhenThereAreTooManyTokens() { @Test void test_parse_ThrowsAnException_WhenTheNameIsNotSpecified() { try { - parser.parse(context(), tokens("deploymentNode")); + parser.parse(new DeploymentEnvironmentDslContext("env"), tokens("deploymentNode")); fail(); } catch (Exception e) { assertEquals("Expected: deploymentNode [description] [technology] [tags] [instances] {", e.getMessage());