Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explicit boundaries for static views #210

Merged
merged 2 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.chriskn.structurizrextension.export

import com.github.chriskn.structurizrextension.export.exporter.ComponentViewExporter
import com.github.chriskn.structurizrextension.export.exporter.ContainerViewExporter
import com.github.chriskn.structurizrextension.export.exporter.DeploymentViewExporter
import com.github.chriskn.structurizrextension.export.exporter.DynamicViewExporter
Expand All @@ -16,6 +17,7 @@ import com.structurizr.model.Container
import com.structurizr.model.DeploymentNode
import com.structurizr.model.Element
import com.structurizr.model.SoftwareSystem
import com.structurizr.view.ComponentView
import com.structurizr.view.ContainerView
import com.structurizr.view.DeploymentView
import com.structurizr.view.DynamicView
Expand Down Expand Up @@ -46,7 +48,14 @@ class ExtendedC4PlantUMLExporter : AbstractDiagramExporter() {
elementWriter,
relationshipWriter
)
private val componentViewExporter = ContainerViewExporter(
private val containerViewExporter = ContainerViewExporter(
boundaryWriter,
footerWriter,
headerWriter,
elementWriter,
relationshipWriter
)
private val componentViewExporter = ComponentViewExporter(
boundaryWriter,
footerWriter,
headerWriter,
Expand Down Expand Up @@ -78,7 +87,9 @@ class ExtendedC4PlantUMLExporter : AbstractDiagramExporter() {

override fun export(view: DeploymentView): Diagram = deploymentViewExporter.exportDeploymentView(view)

override fun export(view: ContainerView): Diagram = componentViewExporter.exportContainerView(view)
override fun export(view: ContainerView): Diagram = containerViewExporter.exportContainerView(view)

override fun export(view: ComponentView): Diagram = componentViewExporter.exportComponentView(view)

override fun createDiagram(view: ModelView, definition: String): Diagram = createC4Diagram(view, definition)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.github.chriskn.structurizrextension.export.exporter

import com.github.chriskn.structurizrextension.export.createC4Diagram
import com.github.chriskn.structurizrextension.export.writer.BoundaryWriter
import com.github.chriskn.structurizrextension.export.writer.ElementWriter
import com.github.chriskn.structurizrextension.export.writer.FooterWriter
import com.github.chriskn.structurizrextension.export.writer.HeaderWriter
import com.github.chriskn.structurizrextension.export.writer.RelationshipWriter
import com.structurizr.export.Diagram
import com.structurizr.export.IndentingWriter
import com.structurizr.model.Component
import com.structurizr.model.Container
import com.structurizr.view.ComponentView
import com.structurizr.view.ElementView
import com.structurizr.view.ModelView

class ComponentViewExporter(
private val boundaryWriter: BoundaryWriter,
private val footerWriter: FooterWriter,
private val headerWriter: HeaderWriter,
private val elementWriter: ElementWriter,
private val relationshipWriter: RelationshipWriter
) {

internal fun exportComponentView(view: ComponentView): Diagram {
val writer = IndentingWriter()
headerWriter.writeHeader(view, writer)

var elementsWritten = false
for (elementView in view.elements) {
if (elementView.element !is Component) {
elementWriter.writeElement(view, elementView.element, writer)
elementsWritten = true
}
}
if (elementsWritten) {
writer.writeLine()
}

val containers: List<Container> = getBoundaryContainer(view)
for (container in containers) {
val showContainerBoundary = view.externalContainerBoundariesVisible
if (showContainerBoundary) {
boundaryWriter.startContainerBoundary(container, writer, view)
}
for (elementView in view.elements) {
if (elementView.element.parent === container) {
elementWriter.writeElement(view, elementView.element, writer)
}
}
if (showContainerBoundary) {
boundaryWriter.endContainerBoundary(writer, view)
} else {
writer.writeLine()
}
}

relationshipWriter.writeRelationships(view, writer)

footerWriter.writeFooter(view, writer)

return createC4Diagram(view, writer.toString())
}

private fun getBoundaryContainer(view: ModelView): List<Container> =
view.elements
.asSequence()
.map { obj: ElementView -> obj.element }
.filterIsInstance<Component>()
.map { it.container }
.toSet()
.toList()
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ class ContainerViewExporter(

val softwareSystems: List<SoftwareSystem> = getBoundarySoftwareSystems(view)
for (softwareSystem in softwareSystems) {
val showSoftwareSystemBoundary =
softwareSystem == view.softwareSystem || view.externalSoftwareSystemBoundariesVisible
val showSoftwareSystemBoundary = view.externalSoftwareSystemBoundariesVisible
if (showSoftwareSystemBoundary) {
boundaryWriter.startSoftwareSystemBoundary(softwareSystem, writer, view)
}
Expand All @@ -63,12 +62,12 @@ class ContainerViewExporter(
return createC4Diagram(view, writer.toString())
}

private fun getBoundarySoftwareSystems(view: ModelView): List<SoftwareSystem> {
val softwareSystems = view.elements
.map { obj: ElementView -> obj.element }
.filterIsInstance<Container>()
.map { it.softwareSystem }
.toSet()
return softwareSystems.sortedBy { it.id }
}
private fun getBoundarySoftwareSystems(view: ModelView): List<SoftwareSystem> = view.elements
.asSequence()
.map { obj: ElementView -> obj.element }
.filterIsInstance<Container>()
.map { it.softwareSystem }
.toSet()
.sortedBy { it.id }
.toList()
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.chriskn.structurizrextension.view

import com.structurizr.view.ComponentView
import com.structurizr.view.ContainerView
import com.structurizr.view.DynamicView

Expand All @@ -16,3 +17,11 @@ var ContainerView.showExternalSoftwareSystemBoundaries: Boolean
@Suppress("DEPRECATION")
this.externalSoftwareSystemBoundariesVisible = value
}

var ComponentView.showExternalContainerBoundaries: Boolean
// naming bug in structurizr
get() = this.externalContainerBoundariesVisible
set(value) {
@Suppress("DEPRECATION")
this.setExternalSoftwareSystemBoundariesVisible(value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,32 @@ import com.github.chriskn.structurizrextension.model.component
import com.github.chriskn.structurizrextension.model.container
import com.github.chriskn.structurizrextension.model.person
import com.github.chriskn.structurizrextension.model.softwareSystem
import com.github.chriskn.structurizrextension.model.usedBy
import com.github.chriskn.structurizrextension.model.uses
import com.github.chriskn.structurizrextension.plantuml.C4PlantUmlLayout
import com.github.chriskn.structurizrextension.plantuml.Layout
import com.github.chriskn.structurizrextension.plantuml.LineType
import com.github.chriskn.structurizrextension.view.componentView
import com.github.chriskn.structurizrextension.view.showExternalContainerBoundaries
import com.structurizr.Workspace
import com.structurizr.model.InteractionStyle
import com.structurizr.model.Location
import org.junit.jupiter.api.Test

class ComponentViewTest {

private val diagramKey = "Component"
private val workspace = Workspace("My Workspace", "")
private val model = workspace.model
private val softwareSystem = model.softwareSystem("My Software System", "system description")
private val backendApplication = softwareSystem.container("Backend App", "some backend app", technology = "Kotlin")

@Test
fun `component diagram is written as expected`() {
val workspace = Workspace("My Workspace", "")
val model = workspace.model
val softwareSystem = model.softwareSystem("My Software System", "system description")
val backendApplication = softwareSystem.container("Backend App", "some backend app", technology = "Kotlin")
init {
val user = model.person("User", "A user", Location.External)
val restController = backendApplication.component(
"MyRestController",
"Provides data via rest",
technology = "REST"
technology = "REST",
usedBy = listOf(Dependency(user, "Website", "REST"))
)
restController.usedBy(user, "Website", "REST")
val repository = backendApplication.component(
"MyRepo",
"Provides CRUD operations for data",
Expand All @@ -49,21 +47,23 @@ class ComponentViewTest {
technology = "",
icon = "kotlin",
usedBy = listOf(Dependency(restController, "calls")),
uses = listOf(
Dependency(
repository,
"gets notified",
interactionStyle = InteractionStyle.Asynchronous,
)
)
)
service.uses(
repository,
"gets notified",
interactionStyle = InteractionStyle.Asynchronous,
)
val backendApp = backendApplication.component(
backendApplication.component(
"Cache",
"In Memory DB",
link = "https://google.de",
technology = "RocksDB",
icon = "rocksdb",
c4Type = C4Type.DATABASE,
usedBy = listOf(Dependency(service, "uses", link = ""))
)
backendApp.usedBy(service, "uses", link = "")
softwareSystem.container(
"Database",
"some database",
Expand All @@ -72,12 +72,76 @@ class ComponentViewTest {
icon = "postgresql",
usedBy = listOf(Dependency(backendApplication.components.first { it.hasTag("repo") }, "gets data from"))
)
val maintainer = model.person(
model.person(
"Maintainer",
"some employee",
location = Location.Internal
location = Location.Internal,
uses = listOf(Dependency(restController, "Admin UI", "REST", icon = "empty"))
)
}

@Test
fun `component diagram is written without boundary if it contains only components`() {
val diagramKey = "ComponentWithoutBoundary"
val componentView = workspace.views.componentView(
backendApplication,
diagramKey,
"Test component view",
C4PlantUmlLayout(
nodeSep = 100,
rankSep = 150,
lineType = LineType.Ortho,
layout = Layout.LeftToRight
)
)
componentView.addAllComponents()

assertExpectedDiagramWasWrittenForView(workspace, diagramKey)
}

@Test
fun `component diagram is written with boundary if container boundaries are visible`() {
val diagramKey = "ComponentWithBoundary"
val componentView = workspace.views.componentView(
backendApplication,
diagramKey,
"Test component view",
C4PlantUmlLayout(
nodeSep = 100,
rankSep = 150,
lineType = LineType.Ortho,
layout = Layout.LeftToRight
)
)
maintainer.uses(restController, "Admin UI", "REST", icon = "empty")
componentView.addAllComponents()
componentView.showExternalContainerBoundaries = true

assertExpectedDiagramWasWrittenForView(workspace, diagramKey)
}

@Test
fun `component diagram is written without boundary if containers are added and boundaries not visible`() {
val diagramKey = "ComponentWithContainers"
val componentView = workspace.views.componentView(
backendApplication,
diagramKey,
"Test component view",
C4PlantUmlLayout(
nodeSep = 100,
rankSep = 150,
lineType = LineType.Ortho,
layout = Layout.LeftToRight
)
)
componentView.addAllComponents()
componentView.addAllContainers()

assertExpectedDiagramWasWrittenForView(workspace, diagramKey)
}

@Test
fun `component diagram is written with boundary if containers are added and container boundaries are visible`() {
val diagramKey = "ComponentWithContainersAndBoundaries"
val componentView = workspace.views.componentView(
backendApplication,
diagramKey,
Expand All @@ -89,7 +153,9 @@ class ComponentViewTest {
layout = Layout.LeftToRight
)
)
componentView.addAllElements()
componentView.addAllComponents()
componentView.addAllContainers()
componentView.showExternalContainerBoundaries = true

assertExpectedDiagramWasWrittenForView(workspace, diagramKey)
}
Expand Down
Loading
Loading