Skip to content

Commit

Permalink
Add support for groups
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsvanspauwen committed Jul 26, 2023
1 parent 897ed1d commit 0670fad
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 55 deletions.
18 changes: 12 additions & 6 deletions example/chatsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
from .users import customer, support_agent

chat = Model("Chat")
chat_system = chat.SoftwareSystem("ChatSystem", "Provides a platform for customer support.")
chat_widget = chat_system.Container("ChatWidget", "Embeddable chat widget for real-time customer support.", technology="JavaScript")
chat_server = chat_system.Container("ChatServer", "Handles message routing and storage.", technology="Node.js")
message_router = chat_server.Component("MessageRouter", "Routes messages between users.", technology="Node.js")
message_store = chat_server.Component("MessageStore", "Stores chat history.", technology="MongoDB", tags=["database"])
slack = chat.SoftwareSystem("Slack", "Third party business chat application")

with chat.Group("Built by Team X") as g:
with g.Group("In Belgium") as g2:
chat_system = g2.SoftwareSystem("ChatSystem", "Provides a platform for customer support.")
with chat_system.Group("Chat System Containers") as g3:
chat_widget = g3.Container("ChatWidget", "Embeddable chat widget for real-time customer support.", technology="JavaScript")
chat_server = g3.Container("ChatServer", "Handles message routing and storage.", technology="Node.js")
message_router = chat_server.Component("MessageRouter", "Routes messages between users.", technology="Node.js")
message_store = chat_server.Component("MessageStore", "Stores chat history.", technology="MongoDB", tags=["database"])

with chat.Group("External Supplier") as g4:
slack = g4.SoftwareSystem("Slack", "Third party business chat application")

chat_widget.uses(chat_server, "Sends/receives messages", technology="xmpp")
message_router.uses(message_store, "Stores messages")
Expand Down
153 changes: 141 additions & 12 deletions pystructurizr/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,31 @@ def dump_relationships(self, dumper: Dumper) -> None:
class Container(Element):
def __init__(self, name: str, description: Optional[str]=None, technology: Optional[str]=None, tags: Optional[List[str]]=None):
super().__init__(name, description, technology, tags)
self.components = []
self.elements = []

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
pass

# pylint: disable=invalid-name
def Component(self, *args, **kwargs) -> Component:
if args and isinstance(args[0], Component):
component = args[0]
else:
component = Component(*args, **kwargs)
self.components.append(component)
self.elements.append(component)
return component

# pylint: disable=invalid-name
def Group(self, *args, **kwargs) -> "Group":
if args and isinstance(args[0], Group):
g = args[0]
else:
g = Group(*args, **kwargs)
self.elements.append(g)
return g

def dump(self, dumper: Dumper) -> None:
dumper.add(f'{self.instname} = Container "{self.name}" "{self.description}" {{')
Expand All @@ -126,31 +141,46 @@ def dump(self, dumper: Dumper) -> None:
dumper.add(f'technology "{self.technology}"')
if self.tags:
dumper.add(f'tags "{", ".join(self.tags)}"')
for component in self.components:
component.dump(dumper)
for el in self.elements:
el.dump(dumper)
dumper.outdent()
dumper.add('}')

def dump_relationships(self, dumper: Dumper) -> None:
for rel in self.relationships:
rel.dump(dumper)
for component in self.components:
component.dump_relationships(dumper)
for el in self.elements:
el.dump_relationships(dumper)


class SoftwareSystem(Element):
def __init__(self, name: str, description: Optional[str]=None, technology: Optional[str]=None, tags: Optional[List[str]]=None):
super().__init__(name, description, technology, tags)
self.containers = []
self.elements = []

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
pass

# pylint: disable=invalid-name
def Container(self, *args, **kwargs) -> Container:
if args and isinstance(args[0], Container):
container = args[0]
else:
container = Container(*args, **kwargs)
self.containers.append(container)
self.elements.append(container)
return container

# pylint: disable=invalid-name
def Group(self, *args, **kwargs) -> "Group":
if args and isinstance(args[0], Group):
g = args[0]
else:
g = Group(*args, **kwargs)
self.elements.append(g)
return g

def dump(self, dumper: Dumper) -> None:
dumper.add(f'{self.instname} = SoftwareSystem "{self.name}" "{self.description}" {{')
Expand All @@ -159,23 +189,102 @@ def dump(self, dumper: Dumper) -> None:
dumper.add(f'technology "{self.technology}"')
if self.tags:
dumper.add(f'tags "{", ".join(self.tags)}"')
for container in self.containers:
container.dump(dumper)
for el in self.elements:
el.dump(dumper)
dumper.outdent()
dumper.add('}')

def dump_relationships(self, dumper: Dumper) -> None:
for rel in self.relationships:
rel.dump(dumper)
for container in self.containers:
container.dump_relationships(dumper)
for el in self.elements:
el.dump_relationships(dumper)


class Group(Element):
def __init__(self, name: str):
super().__init__(name)
self.elements = []

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
pass

# pylint: disable=invalid-name
def Person(self, *args, **kwargs) -> Person:
if args and isinstance(args[0], Person):
person = args[0]
else:
person = Person(*args, **kwargs)
self.elements.append(person)
return person

# pylint: disable=invalid-name
def SoftwareSystem(self, *args, **kwargs) -> SoftwareSystem:
if args and isinstance(args[0], SoftwareSystem):
system = args[0]
else:
system = SoftwareSystem(*args, **kwargs)
self.elements.append(system)
return system

# pylint: disable=invalid-name
def Container(self, *args, **kwargs) -> Container:
if args and isinstance(args[0], Container):
container = args[0]
else:
container = Container(*args, **kwargs)
self.elements.append(container)
return container

# pylint: disable=invalid-name
def Component(self, *args, **kwargs) -> Component:
if args and isinstance(args[0], Component):
component = args[0]
else:
component = Component(*args, **kwargs)
self.elements.append(component)
return component

# pylint: disable=invalid-name
def Group(self, *args, **kwargs) -> "Group":
if args and isinstance(args[0], Group):
g = args[0]
else:
g = Group(*args, **kwargs)
self.elements.append(g)
return g

def dump(self, dumper: Dumper) -> None:
for element in self.elements:
element.dump(dumper)

def dump(self, dumper: Dumper) -> None:
dumper.add(f'group "{self.name}" {{')
dumper.indent()
for element in self.elements:
element.dump(dumper)
dumper.outdent()
dumper.add('}')

def dump_relationships(self, dumper: Dumper) -> None:
for element in self.elements:
element.dump_relationships(dumper)


class Model(Element):
def __init__(self, name: str):
super().__init__(name)
self.elements = []

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
pass

# pylint: disable=invalid-name
def Person(self, *args, **kwargs) -> Person:
if args and isinstance(args[0], Person):
Expand All @@ -193,6 +302,15 @@ def SoftwareSystem(self, *args, **kwargs) -> SoftwareSystem:
system = SoftwareSystem(*args, **kwargs)
self.elements.append(system)
return system

# pylint: disable=invalid-name
def Group(self, *args, **kwargs) -> Group:
if args and isinstance(args[0], Group):
g = args[0]
else:
g = Group(*args, **kwargs)
self.elements.append(g)
return g

def dump(self, dumper: Dumper) -> None:
for element in self.elements:
Expand Down Expand Up @@ -303,12 +421,23 @@ def __init__(self):
}
)

def __enter__(self):
return self

def __exit__(self, exc_type, exc_val, exc_tb):
pass

def dump(self, dumper: Dumper = Dumper()) -> None:
dumper.add('workspace {')
dumper.indent()

dumper.add('model {')
dumper.indent()
dumper.add('properties {')
dumper.indent()
dumper.add('"structurizr.groupSeparator" "/"')
dumper.outdent()
dumper.add('}')
for model in self.models:
model.dump(dumper)
for model in self.models:
Expand Down
75 changes: 38 additions & 37 deletions single_file_example.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
from pystructurizr.dsl import Workspace
from pystructurizr.dsl import Model

users = Model("Users")
customer = users.Person("Customer", "A user of our website.")
support_agent = users.Person("Support Agent", "Handles customer queries.")
analyst = users.Person("Data analyst", "Gathers insight about products and features and reports them to management.")

chat = Model("Chat")
chat_system = chat.SoftwareSystem("ChatSystem", "Provides a platform for customer support.")
chat_widget = chat_system.Container("ChatWidget", "Embeddable chat widget for real-time customer support.", technology="JavaScript")
chat_server = chat_system.Container("ChatServer", "Handles message routing and storage.", technology="Node.js")
message_router = chat_server.Component("MessageRouter", "Routes messages between users.", technology="Node.js")
message_store = chat_server.Component("MessageStore", "Stores chat history.", technology="MongoDB", tags=["database"])
slack = chat.SoftwareSystem("Slack", "Third party business chat application")

chat_widget.uses(chat_server, "Sends/receives messages", technology="xmpp")
message_router.uses(message_store, "Stores messages")
chat_server.uses(slack, "Integrates with")

customer.uses(chat_widget, "Initiates conversations", "chat widget")
support_agent.uses(slack, "Responds to customer requests", "Slack")


workspace = Workspace()
workspace.Model(users)
workspace.Model(chat)

workspace.Styles(
{"tag": "Person", "background": "#08427b", "color": "#ffffff"},
{"tag": "Software System", "background": "#728da1", "color": "#000000"},
{"tag": "Container", "background": "#728da1", "color": "#000000"},
{"tag": "Component", "background": "#728da1", "color": "#000000"},
)

workspace.ComponentView(
chat_server,
"Component View",
"The component view of the MessageRouter."
)
with Model("Users") as users:
customer = users.Person("Customer", "A user of our website.")
support_agent = users.Person("Support Agent", "Handles customer queries.")
analyst = users.Person("Data analyst", "Gathers insight about products and features and reports them to management.")

with Model("Chat") as chat:
with chat.Group("Our system") as g:
chat_system = g.SoftwareSystem("ChatSystem", "Provides a platform for customer support.")
chat_widget = chat_system.Container("ChatWidget", "Embeddable chat widget for real-time customer support.", technology="JavaScript")
chat_server = chat_system.Container("ChatServer", "Handles message routing and storage.", technology="Node.js")
message_router = chat_server.Component("MessageRouter", "Routes messages between users.", technology="Node.js")
message_store = chat_server.Component("MessageStore", "Stores chat history.", technology="MongoDB", tags=["database"])

with chat.Group("External systems") as g2:
slack = g2.SoftwareSystem("Slack", "Third party business chat application")

chat_widget.uses(chat_server, "Sends/receives messages", technology="xmpp")
message_router.uses(message_store, "Stores messages")
chat_server.uses(slack, "Integrates with")

customer.uses(chat_widget, "Initiates conversations", "chat widget")
support_agent.uses(slack, "Responds to customer requests", "Slack")

with Workspace() as workspace:
workspace.Model(users)
workspace.Model(chat)

workspace.Styles(
{"tag": "Person", "background": "#08427b", "color": "#ffffff"},
{"tag": "Software System", "background": "#728da1", "color": "#000000"},
{"tag": "Container", "background": "#728da1", "color": "#000000"},
{"tag": "Component", "background": "#728da1", "color": "#000000"},
)

workspace.SystemLandscapeView(
"System View",
"The system-level view of the chat support system."
)

0 comments on commit 0670fad

Please sign in to comment.