Skip to content

Commit abffa83

Browse files
yt-msMidnighter
authored andcommitted
feat: implement SoftwareSystemInstance
1 parent 6688cbd commit abffa83

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed

src/structurizr/model/deployment_node.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from .container_instance import ContainerInstance, ContainerInstanceIO
2323
from .deployment_element import DeploymentElement, DeploymentElementIO
24+
from .software_system_instance import SoftwareSystemInstance, SoftwareSystemInstanceIO
2425

2526

2627
if TYPE_CHECKING:
@@ -59,6 +60,9 @@ class Config:
5960
container_instances: List[ContainerInstanceIO] = Field(
6061
default=(), alias="containerInstances"
6162
)
63+
software_system_instances: List[SoftwareSystemInstanceIO] = Field(
64+
default=(), alias="softwareSystemInstances"
65+
)
6266

6367

6468
DeploymentNodeIO.update_forward_refs()
@@ -89,6 +93,7 @@ def __init__(
8993
instances: int = 1,
9094
children: Iterable["DeploymentNode"] = (),
9195
container_instances: Iterable[ContainerInstance] = (),
96+
software_system_instances: Iterable[SoftwareSystemInstance] = (),
9297
**kwargs,
9398
) -> None:
9499
"""Initialize a deployment node."""
@@ -98,6 +103,7 @@ def __init__(
98103
self.instances = instances
99104
self._children = set(children)
100105
self._container_instances = set(container_instances)
106+
self._software_system_instances = set(software_system_instances)
101107

102108
@property
103109
def children(self) -> Iterable["DeploymentNode"]:
@@ -109,6 +115,11 @@ def container_instances(self) -> Iterable[ContainerInstance]:
109115
"""Return read-only list of container instances."""
110116
return list(self._container_instances)
111117

118+
@property
119+
def software_system_instances(self) -> Iterable[SoftwareSystemInstance]:
120+
"""Return read-only list of software system instances."""
121+
return list(self._software_system_instances)
122+
112123
def add_deployment_node(self, **kwargs) -> "DeploymentNode":
113124
"""Add a new child deployment node to this node."""
114125
node = DeploymentNode(**kwargs)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright (c) 2020, Moritz E. Beber.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
"""Provide a softwrae system instance model."""
17+
18+
19+
from typing import TYPE_CHECKING
20+
21+
from pydantic import Field
22+
23+
from .software_system import SoftwareSystem
24+
from .static_structure_element_instance import (
25+
StaticStructureElementInstance,
26+
StaticStructureElementInstanceIO,
27+
)
28+
29+
30+
if TYPE_CHECKING:
31+
from .model import Model
32+
33+
34+
__all__ = ("SoftwareSystemInstance", "SoftwareSystemInstanceIO")
35+
36+
37+
class SoftwareSystemInstanceIO(StaticStructureElementInstanceIO):
38+
"""Represents a software system instance which can be added to a deployment node."""
39+
40+
software_system_id: str = Field(alias="softwareSystemId")
41+
42+
43+
class SoftwareSystemInstance(StaticStructureElementInstance):
44+
"""Represents a software system instance which can be added to a deployment node."""
45+
46+
def __init__(self, *, software_system: SoftwareSystem, **kwargs) -> None:
47+
"""Initialize a software system instance."""
48+
super().__init__(element=software_system, **kwargs)
49+
self.software_system = software_system
50+
51+
@property
52+
def software_system_id(self) -> str:
53+
"""Return the ID of the software system for this instance."""
54+
return self.software_system.id
55+
56+
@classmethod
57+
def hydrate(
58+
cls,
59+
system_instance_io: SoftwareSystemInstanceIO,
60+
model: "Model",
61+
) -> "SoftwareSystemInstance":
62+
"""Hydrate a new SoftwareSystemInstance instance from its IO.
63+
64+
This will also automatically register with the model.
65+
"""
66+
system = model.get_element(system_instance_io.software_system_id)
67+
instance = cls(
68+
**cls.hydrate_arguments(system_instance_io),
69+
software_system=system,
70+
)
71+
model += instance
72+
return instance
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# https://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
14+
"""Ensure the expected behaviour of the software_system element."""
15+
16+
17+
import pytest
18+
19+
from structurizr.model.http_health_check import HTTPHealthCheck, HTTPHealthCheckIO
20+
from structurizr.model.software_system_instance import (
21+
SoftwareSystemInstance,
22+
SoftwareSystemInstanceIO,
23+
)
24+
25+
26+
class MockModel:
27+
"""Implement a mock model for testing."""
28+
29+
def __init__(self):
30+
"""Create an mock software_system for testing."""
31+
self.mock_system = MockSystem()
32+
33+
def __iadd__(self, instance: SoftwareSystemInstance):
34+
"""Simulate the model assigning IDs to new elements."""
35+
if not instance.id:
36+
instance.id = "id"
37+
instance.set_model(self)
38+
return self
39+
40+
def get_element(self, id: str):
41+
"""Simulate fetching the software_system by ID."""
42+
return self.mock_system
43+
44+
45+
class MockSystem:
46+
"""Implement a mock system for testing."""
47+
48+
def __init__(self):
49+
"""Create a new mock."""
50+
self.id = "19"
51+
self.name = "Mock System"
52+
53+
54+
@pytest.fixture(scope="function")
55+
def model_with_system() -> MockModel:
56+
"""Provide a mock model for test cases to use."""
57+
return MockModel()
58+
59+
60+
@pytest.mark.parametrize(
61+
"attributes",
62+
[
63+
pytest.param({}, marks=pytest.mark.raises(exception=TypeError)),
64+
{
65+
"environment": "Development",
66+
"software_system": MockSystem(),
67+
"instance_id": "17",
68+
"properties": {"a": "b"},
69+
},
70+
],
71+
)
72+
def test_software_system_instance_init(attributes):
73+
"""Expect proper initialization from arguments."""
74+
instance = SoftwareSystemInstance(**attributes)
75+
for attr, expected in attributes.items():
76+
assert getattr(instance, attr) == expected
77+
78+
79+
def test_software_system_instance_hydration_retrieves_software_system_from_id(
80+
model_with_system,
81+
):
82+
"""Check that the software_system instance is retrieved on hydration."""
83+
io = SoftwareSystemInstanceIO(
84+
software_system_id="19", instance_id=3, environment="Live"
85+
)
86+
87+
instance = SoftwareSystemInstance.hydrate(io, model_with_system)
88+
89+
assert instance.instance_id == 3
90+
assert instance.environment == "Live"
91+
assert instance.software_system is model_with_system.mock_system
92+
assert instance.software_system_id == "19"
93+
94+
95+
def test_software_system_instance_name_is_software_system_name(model_with_system):
96+
"""Ensure software_system instance takes its name from its software system."""
97+
io = SoftwareSystemInstanceIO(
98+
software_system_id="19", instance_id=3, environment="Live"
99+
)
100+
101+
instance = SoftwareSystemInstance.hydrate(io, model_with_system)
102+
103+
assert instance.name == "Mock System"
104+
105+
106+
def test_software_system_instance_hyrdates_http_health_checks(model_with_system):
107+
"""Check that health checks are hydrated into the SoftwareSystemInstance."""
108+
health_check_io = HTTPHealthCheckIO(name="name", url="http://a.b.com")
109+
io = SoftwareSystemInstanceIO(
110+
software_system_id="19",
111+
instance_id=3,
112+
environment="Live",
113+
health_checks=[health_check_io],
114+
)
115+
116+
instance = SoftwareSystemInstance.hydrate(io, model_with_system)
117+
118+
assert len(instance.health_checks) == 1
119+
assert list(instance.health_checks)[0].name == "name"
120+
121+
122+
def test_software_system_instance_serialization(model_with_system):
123+
"""Test that system instances serialise properly."""
124+
health_check = HTTPHealthCheck(name="health", url="http://a.b.com")
125+
instance = SoftwareSystemInstance(
126+
software_system=model_with_system.mock_system,
127+
instance_id=7,
128+
health_checks=[health_check],
129+
)
130+
131+
io = SoftwareSystemInstanceIO.from_orm(instance)
132+
133+
assert io.software_system_id == "19"
134+
assert io.instance_id == 7
135+
assert len(io.health_checks) == 1
136+
assert io.health_checks[0].name == "health"

0 commit comments

Comments
 (0)