Skip to content

Commit

Permalink
Adds the ability to scope and validate a workspace.
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbrowndotje committed Nov 19, 2023
1 parent 7d55bbb commit c73ecff
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Adds a flag to determine whether automatic layout has been applied or not.
- Adds support for perspective values.
- Adds the ability to scope and validate a workspace.

## 1.27.0 (23rd October 2023)

Expand Down
20 changes: 20 additions & 0 deletions structurizr-core/src/com/structurizr/Workspace.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public final class Workspace extends AbstractWorkspace implements Documentable {

private static final Log log = LogFactory.getLog(Workspace.class);

private WorkspaceScope scope = null;

private Model model = createModel();
private ViewSet viewSet;
private Documentation documentation;
Expand All @@ -44,6 +46,24 @@ public Workspace(String name, String description) {
documentation = new Documentation();
}

/**
* Gets the scope of this workspace
*
* @return a WorkspaceScope enum, or null if undefined
*/
public WorkspaceScope getScope() {
return scope;
}

/**
* Sets the workspace scope.
*
* @param scope a WorkspaceScope enum, or null if undefined
*/
public void setScope(WorkspaceScope scope) {
this.scope = scope;
}

/**
* Gets the software architecture model.
*
Expand Down
8 changes: 8 additions & 0 deletions structurizr-core/src/com/structurizr/WorkspaceScope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.structurizr;

public enum WorkspaceScope {

Landscape,
SoftwareSystem

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.structurizr.validation;

import com.structurizr.Workspace;
import com.structurizr.model.Model;
import com.structurizr.model.SoftwareSystem;

/**
* Validates that the workspace does not define containers and software system level documentation.
*/
public class LandscapeWorkspaceScopeValidator implements WorkspaceScopeValidator {

@Override
public void validate(Workspace workspace) throws WorkspaceScopeValidationException {
Model model = workspace.getModel();
for (SoftwareSystem softwareSystem : model.getSoftwareSystems()) {
if (softwareSystem.getContainers().size() > 0) {
throw new WorkspaceScopeValidationException("Workspace is landscape scoped, but the software system named " + softwareSystem.getName() + " has containers.");
}

if (!softwareSystem.getDocumentation().isEmpty()) {
throw new WorkspaceScopeValidationException("Workspace is landscape scoped, but the software system named " + softwareSystem.getName() + " has documentation.");
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.structurizr.validation;

import com.structurizr.Workspace;
import com.structurizr.model.Model;

/**
* Validates that the workspace only defines detail (containers and documentation) for a single software system.
*/
public class SoftwareSystemWorkspaceScopeValidator implements WorkspaceScopeValidator {

@Override
public void validate(Workspace workspace) throws WorkspaceScopeValidationException {
Model model = workspace.getModel();
long softwareSystemsWithContainersOrDocumentation = model.getSoftwareSystems().stream().filter(ss -> ss.getContainers().size() > 0 || !ss.getDocumentation().isEmpty()).count();

if (softwareSystemsWithContainersOrDocumentation > 1) {
throw new WorkspaceScopeValidationException("Workspace is software system scoped, but multiple software systems have containers and/or documentation defined.");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.structurizr.validation;

import com.structurizr.Workspace;

public class UndefinedWorkspaceScopeValidator implements WorkspaceScopeValidator {

@Override
public void validate(Workspace workspace) throws WorkspaceScopeValidationException {
// no-op
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.structurizr.validation;

public class WorkspaceScopeValidationException extends Exception {

public WorkspaceScopeValidationException(String message) {
super(message);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.structurizr.validation;

import com.structurizr.Workspace;

public interface WorkspaceScopeValidator {

void validate(Workspace workspace) throws WorkspaceScopeValidationException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.structurizr.validation;

import com.structurizr.Workspace;
import com.structurizr.WorkspaceScope;

public final class WorkspaceScopeValidatorFactory {

public static WorkspaceScopeValidator getValidator(Workspace workspace) {
if (workspace.getScope() == WorkspaceScope.Landscape) {
return new LandscapeWorkspaceScopeValidator();
} else if (workspace.getScope() == WorkspaceScope.SoftwareSystem) {
return new SoftwareSystemWorkspaceScopeValidator();
} else {
return new UndefinedWorkspaceScopeValidator();
}
}

}
12 changes: 12 additions & 0 deletions structurizr-core/test/unit/com/structurizr/WorkspaceTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,16 @@ void hydrate_DoesNotCrash() {
workspace.hydrate();
}

@Test
void scope() {
Workspace workspace = new Workspace("Name", "Description");
assertNull(workspace.getScope()); // default scope is undefined

workspace.setScope(WorkspaceScope.SoftwareSystem);
assertEquals(WorkspaceScope.SoftwareSystem, workspace.getScope());

workspace.setScope(null);
assertNull(workspace.getScope());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.structurizr.validation;

import com.structurizr.Workspace;
import com.structurizr.documentation.Format;
import com.structurizr.documentation.Section;
import com.structurizr.validation.LandscapeWorkspaceScopeValidator;
import com.structurizr.validation.WorkspaceScopeValidationException;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public class LandscapeWorkspaceScopeValidatorTests {

private final LandscapeWorkspaceScopeValidator validator = new LandscapeWorkspaceScopeValidator();

@Test
void validate() throws Exception {
Workspace workspace = new Workspace("Name", "Description");
validator.validate(workspace);

workspace.getModel().addPerson("User");
validator.validate(workspace);

workspace.getModel().addSoftwareSystem("A");
validator.validate(workspace);

workspace.getModel().addSoftwareSystem("B");
validator.validate(workspace);
}

@Test
void validate_ThrowsAnException_WhenContainersAreDefined() {
Workspace workspace = new Workspace("Name", "Description");
workspace.getModel().addSoftwareSystem("A").addContainer("AA");
try {
validator.validate(workspace);
fail();
} catch (WorkspaceScopeValidationException e) {
assertEquals("Workspace is landscape scoped, but the software system named A has containers.", e.getMessage());
}
}

@Test
void validate_ThrowsAnException_WhenSoftwareSystemDocumentationIsDefined() {
Workspace workspace = new Workspace("Name", "Description");
workspace.getModel().addSoftwareSystem("A").getDocumentation().addSection(new Section(Format.Markdown, "## Heading 1"));
try {
validator.validate(workspace);
fail();
} catch (WorkspaceScopeValidationException e) {
assertEquals("Workspace is landscape scoped, but the software system named A has documentation.", e.getMessage());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.structurizr.validation;

import com.structurizr.Workspace;
import com.structurizr.documentation.Format;
import com.structurizr.documentation.Section;
import com.structurizr.model.SoftwareSystem;
import com.structurizr.validation.SoftwareSystemWorkspaceScopeValidator;
import com.structurizr.validation.WorkspaceScopeValidationException;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public class SoftwareSystemWorkspaceScopeValidatorTests {

private final SoftwareSystemWorkspaceScopeValidator validator = new SoftwareSystemWorkspaceScopeValidator();

@Test
void validate() throws Exception {
Workspace workspace = new Workspace("Name", "Description");
validator.validate(workspace);

workspace.getModel().addPerson("User");
validator.validate(workspace);

SoftwareSystem a = workspace.getModel().addSoftwareSystem("A");
a.addContainer("AA");
a.getDocumentation().addSection(new Section(Format.Markdown, "## Heading 1"));
validator.validate(workspace);

workspace.getModel().addSoftwareSystem("B");
validator.validate(workspace);
}

@Test
void validate_ThrowsAnException_WhenMultipleSoftwareSystemsDefineContainers() {
Workspace workspace = new Workspace("Name", "Description");
workspace.getModel().addSoftwareSystem("A").addContainer("AA");
workspace.getModel().addSoftwareSystem("B").addContainer("BB");
try {
validator.validate(workspace);
fail();
} catch (WorkspaceScopeValidationException e) {
assertEquals("Workspace is software system scoped, but multiple software systems have containers and/or documentation defined.", e.getMessage());
}
}

@Test
void validate_ThrowsAnException_WhenMultipleSoftwareSystemsDefineDocumentation() {
Workspace workspace = new Workspace("Name", "Description");
workspace.getModel().addSoftwareSystem("A").getDocumentation().addSection(new Section(Format.Markdown, "## Heading 1"));
workspace.getModel().addSoftwareSystem("B").getDocumentation().addSection(new Section(Format.Markdown, "## Heading 1"));
try {
validator.validate(workspace);
fail();
} catch (WorkspaceScopeValidationException e) {
assertEquals("Workspace is software system scoped, but multiple software systems have containers and/or documentation defined.", e.getMessage());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.structurizr.validation;

import com.structurizr.Workspace;
import com.structurizr.WorkspaceScope;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

public class WorkspaceScopeValidatorFactoryTests {

@Test
void getValidator() {
Workspace workspace = new Workspace("Name", "Description");
assertTrue(WorkspaceScopeValidatorFactory.getValidator(workspace) instanceof UndefinedWorkspaceScopeValidator);

workspace.setScope(WorkspaceScope.Landscape);
assertTrue(WorkspaceScopeValidatorFactory.getValidator(workspace) instanceof LandscapeWorkspaceScopeValidator);

workspace.setScope(WorkspaceScope.SoftwareSystem);
assertTrue(WorkspaceScopeValidatorFactory.getValidator(workspace) instanceof SoftwareSystemWorkspaceScopeValidator);
}

}

0 comments on commit c73ecff

Please sign in to comment.