コード例 #1
0
def test_interact_with_workspace_without_encryption(settings):
    # Set up a toy workspace.
    workspace = Workspace(
        id=settings.workspace_id,
        name="e2e-tests - without encryption",
        description="A test workspace for the structurizr-python client.",
    )
    system = workspace.model.add_software_system(
        name="Software System", description="Description"
    )
    person = workspace.model.add_person(name="Person", description="Description")
    person.uses(system, "")
    context_view = workspace.views.create_system_context_view(
        software_system=system, key="SystemContext", description="Description"
    )
    context_view.add_all_elements()
    # Upload the workspace and immediately retrieve it again.
    with StructurizrClient(settings=settings) as client:
        client.put_workspace(workspace)
        remote_ws = client.get_workspace()
    # Verify the remote workspace.
    assert remote_ws.model.get_software_system_with_name("Software System") is not None
    assert remote_ws.model.get_person_with_name("Person") is not None
    assert len(remote_ws.model.relationships) == 1
    assert len(remote_ws.views.get_system_context_views()) == 1
    # Verify the generated archive.
    archives = list(settings.workspace_archive_location.glob("*.json.gz"))
    assert len(archives) == 1
    archived_ws: Workspace = Workspace.load(archives[0])
    assert archived_ws.id == 20081
    assert archived_ws.name == "structurizr-python e2e-tests - without encryption"
    assert len(archived_ws.model.software_systems) == 1
コード例 #2
0
def test_workspace_overridding_zip_flag(monkeypatch, tmp_path: Path):
    """Test that default zipping can be overridden explicitly."""
    monkeypatch.syspath_prepend(EXAMPLES)
    example = import_module("getting_started")
    workspace = example.main()

    filepath = tmp_path / "test_workspace.json.gz"

    workspace.dump(filepath, zip=False)
    contents = filepath.read_text()
    assert "My software system" in contents

    # Make sure can be loaded even though its not zipped and ends with .gz
    Workspace.load(filepath)
コード例 #3
0
def main() -> Workspace:
    """Create the 'getting started' example."""
    workspace = Workspace(
        name="Getting Started", description="This is a model of my software system.",
    )

    model = workspace.model

    user = model.add_person(name="User", description="A user of my software system.")
    software_system = model.add_software_system(
        name="Software System", description="My software system."
    )
    user.uses(software_system, "Uses")

    context_view = workspace.views.create_system_context_view(
        software_system=software_system,
        key="SystemContext",
        description="An example of a System Context diagram.",
    )
    context_view.add_all_elements()
    context_view.paper_size = PaperSize.A5_Landscape

    styles = workspace.views.configuration.styles
    styles.add(
        ElementStyle(tag=Tags.SOFTWARE_SYSTEM, background="#1168bd", color="#ffffff")
    )
    styles.add(
        ElementStyle(
            tag=Tags.PERSON, background="#08427b", color="#ffffff", shape=Shape.Person,
        )
    )
    return workspace
コード例 #4
0
def test_serialize_workspace(example, filename, monkeypatch):
    """Expect that ."""
    monkeypatch.syspath_prepend(EXAMPLES)
    example = import_module(example)
    path = DEFINITIONS / filename
    expected = Workspace.load(path)
    assert example.main() == expected
コード例 #5
0
def test_load_workspace_from_bytes(monkeypatch):
    """Test loading from bytes rather than string."""
    path = DEFINITIONS / "GettingStarted.json"
    with open(path, mode="rb") as file:
        binary_content = file.read()

    workspace = Workspace.loads(binary_content)

    assert workspace.model.software_systems != set()
コード例 #6
0
def test_serialize_workspace(example, filename, monkeypatch):
    """Expect that ."""
    monkeypatch.syspath_prepend(EXAMPLES)
    example = import_module(example)
    path = DEFINITIONS / filename
    # TODO (midnighter): Use `from_orm` like `.construct` bypassing validation. (
    #  Requires a pull request on pydantic.)
    expected = WorkspaceIO.from_orm(Workspace.load(path))
    actual = WorkspaceIO.from_orm(example.main())
    assert json.loads(actual.json()) == json.loads(expected.json())
コード例 #7
0
def test_empty_workspace_without_encryption(settings):
    workspace = Workspace(
        id=settings.workspace_id,
        name="e2e-tests - without encryption",
        description="A test workspace for the structurizr-python client.",
    )
    with StructurizrClient(settings=settings) as client:
        client.put_workspace(workspace)
        remote_ws = client.get_workspace()
    assert remote_ws.name == workspace.name
    assert remote_ws.description == workspace.description
コード例 #8
0
def test_save_and_load_workspace_to_string(monkeypatch):
    """Test saving as a JSON string and reloading."""
    monkeypatch.syspath_prepend(EXAMPLES)
    example = import_module("getting_started")
    workspace = example.main()

    json_string: str = workspace.dumps(indent=2)
    workspace2 = Workspace.loads(json_string)

    expected = WorkspaceIO.from_orm(workspace)
    actual = WorkspaceIO.from_orm(workspace2)
    assert json.loads(actual.json()) == json.loads(expected.json())
def test_model_deserialises_deployment_nodes(filename: str):
    """Ensure deserialisaton of deployment nodes works."""
    path = DEFINITIONS / filename
    workspace = Workspace.load(path)
    model = workspace.model

    db_server = model.get_element("59")
    assert db_server.name == "Docker Container - Database Server"
    assert db_server is not None
    assert db_server.model is model
    assert db_server.parent.name == "Developer Laptop"
    assert db_server.parent.parent is None
コード例 #10
0
def test_save_and_load_workspace_to_gzipped_file(monkeypatch, tmp_path: Path):
    """Test saving as a zipped JSON file and reloading."""
    monkeypatch.syspath_prepend(EXAMPLES)
    example = import_module("getting_started")
    workspace = example.main()

    filepath = tmp_path / "test_workspace.json.gz"

    workspace.dump(filepath)
    workspace2 = Workspace.load(filepath)

    expected = WorkspaceIO.from_orm(workspace)
    actual = WorkspaceIO.from_orm(workspace2)
    assert json.loads(actual.json()) == json.loads(expected.json())
コード例 #11
0
def test_loading_workspace_with_groups():
    """Check loading an example workspace with groupings defined."""
    path = DEFINITIONS / "Grouping.json"

    workspace = Workspace.load(path)
    consumer_a = workspace.model.get_element("1")
    consumer_d = workspace.model.get_element("4")
    assert consumer_a.name == "Consumer A"
    assert consumer_a.group == "Consumers - Group 1"
    assert consumer_d.name == "Consumer D"
    assert consumer_d.group == "Consumers - Group 2"

    service_2_api = workspace.model.get_element("9")
    assert service_2_api.name == "Service 2 API"
    assert service_2_api.group == "Service 2"
コード例 #12
0
def test_relationships_after_deserialisation_are_consistent(filename: str):
    """Ensure deserialisaton leaves realationships consistent."""
    path = DEFINITIONS / filename
    workspace = Workspace.load(path)
    model = workspace.model
    atm = model.get_software_system_with_id("9")
    mainframe = model.get_software_system_with_id("4")
    customer = model.get_element("1")

    assert len(atm.relationships) == 1
    assert (len(list(atm.get_relationships())) == 2
            )  # One to mainframe, one from personal banking customer
    assert len(list(atm.get_afferent_relationships())) == 1
    assert next(atm.get_afferent_relationships()).source is customer
    assert len(list(atm.get_efferent_relationships())) == 1
    assert next(atm.get_efferent_relationships()).destination is mainframe
コード例 #13
0
def workspace():
    from structurizr import Workspace
    return Workspace("Name", "Description")
コード例 #14
0
def empty_workspace() -> Workspace:
    """Provide an empty Workspace on demand for test cases to use."""
    return Workspace(name="Name", description="Description")
コード例 #15
0
def main() -> Workspace:
    """Create the financial risk system example."""

    ALERT_TAG = "Alert"

    workspace = Workspace(
        name="Financial Risk System",
        description=
        "This is a simple (incomplete) example C4 model based upon the "
        "financial risk system architecture kata, which can be found at "
        "https://structurizr.com/help/examples.",
    )

    model = workspace.model

    financial_risk_system = model.add_software_system(
        name="Financial Risk System",
        description="Calculates the bank's exposure to risk for product X.",
    )

    business_user = model.add_person(name="Business User",
                                     description="A regular business user.")
    business_user.uses(destination=financial_risk_system,
                       description="Views reports using")

    configuration_user = model.add_person(
        name="Configuration User",
        description=
        "A regular business user who can also configure the parameters "
        "used in the risk calculations.",
    )
    configuration_user.uses(destination=financial_risk_system,
                            description="Configures parameters using")

    trade_data_system = model.add_software_system(
        name="Trade Data System",
        description="The system of record for trades of type X.",
    )
    financial_risk_system.uses(destination=trade_data_system,
                               description="Gets trade data from")

    reference_data_system = model.add_software_system(
        name="Reference Data System",
        description=
        "Manages reference data for all counterparties the bank interacts "
        "with.",
    )
    financial_risk_system.uses(destination=reference_data_system,
                               description="Gets counterparty data from")

    reference_data_system_v2 = model.add_software_system(
        name="Reference Data System v2.0",
        description=
        "Manages reference data for all counterparties the bank interacts "
        "with.",
    )
    reference_data_system_v2.tags.add("Future State")
    financial_risk_system.uses(
        destination=reference_data_system_v2,
        description="Gets counterparty data from").tags.add("Future State")

    email_system = model.add_software_system(
        name="E-mail system",
        description="The bank's Microsoft Exchange system.")
    financial_risk_system.uses(
        destination=email_system,
        description="Sends a notification that a report is ready to",
    )
    email_system.delivers(
        destination=business_user,
        description="Sends a notification that a report is ready to",
        technology="E-mail message",
        interaction_style=InteractionStyle.Asynchronous,
    )

    central_monitoring_service = model.add_software_system(
        name="Central Monitoring Service",
        description="The bank's central monitoring and alerting dashboard.",
    )
    financial_risk_system.uses(
        destination=central_monitoring_service,
        description="Sends critical failure alerts to",
        technology="SNMP",
        interaction_style=InteractionStyle.Asynchronous,
    ).tags.add(ALERT_TAG)

    active_directory = model.add_software_system(
        name="Active Directory",
        description="The bank's authentication and authorisation system.",
    )
    financial_risk_system.uses(
        destination=active_directory,
        description="Uses for user authentication and authorisation",
    )

    views = workspace.views
    contextView = views.create_system_context_view(
        software_system=financial_risk_system,
        key="Context",
        description=(
            "An example System Context diagram for the Financial Risk System "
            "architecture kata."),
    )
    contextView.add_all_software_systems()
    contextView.add_all_people()

    styles = views.configuration.styles
    financial_risk_system.tags.add("Risk System")

    styles.add(ElementStyle(tag=Tags.ELEMENT, color="#ffffff", font_size=34))
    styles.add(
        ElementStyle(tag="Risk System", background="#550000", color="#ffffff"))
    styles.add(
        ElementStyle(
            tag=Tags.SOFTWARE_SYSTEM,
            width=650,
            height=400,
            background="#801515",
            shape=Shape.RoundedBox,
        ))
    styles.add(
        ElementStyle(tag=Tags.PERSON,
                     width=550,
                     background="#d46a6a",
                     shape=Shape.Person))

    styles.add(
        RelationshipStyle(tag=Tags.RELATIONSHIP,
                          thickness=4,
                          dashed=False,
                          font_size=32,
                          width=400))
    styles.add(RelationshipStyle(tag=Tags.SYNCHRONOUS, dashed=False))
    styles.add(RelationshipStyle(tag=Tags.ASYNCHRONOUS, dashed=True))
    styles.add(RelationshipStyle(tag=ALERT_TAG, color="#ff0000"))
    styles.add(
        ElementStyle(tag="Future State", opacity=30, border=Border.Dashed))
    styles.add(RelationshipStyle(tag="Future State", opacity=30, dashed=True))

    return workspace
コード例 #16
0
def test_deserialize_workspace(filename):
    """Expect that a trivial workspace definition is successfully deserialized."""
    path = DEFINITIONS / filename
    Workspace.load(path)
コード例 #17
0
def test_load_unknown_file_raises_file_not_found():
    """Test that attempting to load a non-existent file raises FileNotFound."""
    with pytest.raises(FileNotFoundError):
        Workspace.load("foobar.json")
コード例 #18
0
def main():
    """Create the financial risk system example."""
    workspace = Workspace(
        name="Financial Risk System",
        description="This is a simple (incomplete) example C4 model based upon the "
        "financial risk system architecture kata which can be found at "
        "https://structurizr.com/help/examples.",
    )

    model = workspace.model

    financial_risk_system = model.add_software_system(
        name="Financial Risk System",
        description="Calculates the bank's exposure to risk for product X.",
    )

    business_user = model.add_person(
        name="Business User", description="A regular business user."
    )
    business_user.uses(
        destination=financial_risk_system, description="View reports using"
    )

    configuration_user = model.add_person(
        name="Configuration User",
        description="A regular business user who can also configure the parameters "
        "used in the risk calculations.",
    )
    configuration_user.uses(
        destination=financial_risk_system, description="Configures parameters using"
    )

    trade_data_system = model.add_software_system(
        name="Trade Data System",
        description="The system of record for trades of type X.",
    )
    financial_risk_system.uses(
        destination=trade_data_system, description="Gets trade data from"
    )

    reference_data_system = model.add_software_system(
        name="Reference Data System",
        description="Manages reference data for all counterparties the bank interacts "
        "with.",
    )
    financial_risk_system.uses(
        destination=reference_data_system, description="Gets counterparty data from"
    )

    reference_data_system_v2 = model.add_software_system(
        name="Reference Data System v2.0",
        description="Manages reference data for all counterparties the bank interacts "
        "with.",
    )
    reference_data_system_v2.add_tags("Future State")
    financial_risk_system.uses(
        destination=reference_data_system_v2, description="Gets counterparty data from"
    ).add_tags("Future State")

    email_system = model.add_software_system(
        name="E-mail system", description="The bank's Microsoft Exchange system."
    )
    financial_risk_system.uses(
        destination=email_system,
        description="Sends a notification that a report is ready to",
    )
    email_system.delivers(
        destination=business_user,
        description="Sends a notification that a report is ready to",
        technology="E-mail message",
        interaction_style=InteractionStyle.Asynchronous,
    )

    central_monitoring_service = model.add_software_system(
        name="Central Monitoring Service",
        description="The bank's central monitoring and alerting dashboard.",
    )
    financial_risk_system.uses(
        destination=central_monitoring_service,
        description="Sends critical failure alerts to",
        technology="SNMP",
        interaction_style=InteractionStyle.Asynchronous,
    ).add_tags(TAG_ALERT)

    active_directory = model.add_software_system(
        name="Active Directory",
        description="The bank's authentication and authorisation system.",
    )
    financial_risk_system.uses(
        destination=active_directory,
        description="Uses for user authentication and authorisation",
    )
コード例 #19
0
def create_big_bank_workspace():
    """Create the big bank example."""

    workspace = Workspace(
        name="Big Bank plc",
        description=(
            "This is an example workspace to illustrate the key features of "
            "Structurizr, based around a fictional online banking system."
        ),
    )

    existing_system_tag = "Existing System"
    bank_staff_tag = "Bank Staff"
    web_browser_tag = "Web Browser"
    mobile_app_tag = "Mobile App"
    database_tag = "Database"
    failover_tag = "Failover"

    model = workspace.model
    views = workspace.views

    model.enterprise = Enterprise(name="Big Bank plc")

    # people and software systems
    customer = model.add_person(
        location=Location.External,
        name="Personal Banking Customer",
        description="A customer of the bank, with personal bank accounts.",
        id="customer",
    )

    internet_banking_system = model.add_software_system(
        location=Location.Internal,
        name="Internet Banking System",
        description=(
            "Allows customers to view information about "
            "their bank accounts, and make payments."
        ),
        id="internetBankingSystem",
    )
    customer.uses(
        internet_banking_system, "Views account balances, and makes payments using"
    )

    mainframe_banking_system = model.add_software_system(
        location=Location.Internal,
        name="Mainframe Banking System",
        description=(
            "Stores all of the core banking information "
            "about customers, accounts, transactions, etc."
        ),
        id="mainframe",
    )
    mainframe_banking_system.tags.add(existing_system_tag)
    internet_banking_system.uses(
        mainframe_banking_system,
        "Gets account information from, and makes payments using",
    )

    email_system = model.add_software_system(
        location=Location.Internal,
        name="E-mail System",
        description="The internal Microsoft Exchange e-mail system.",
        id="email",
    )
    internet_banking_system.uses(
        destination=email_system,
        description="Sends e-mail using",
    )
    email_system.tags.add(existing_system_tag)
    email_system.delivers(
        destination=customer,
        description="Sends e-mails to",
    )

    atm = model.add_software_system(
        location=Location.Internal,
        name="ATM",
        description="Allows customers to withdraw cash.",
        id="atm",
    )
    atm.tags.add(existing_system_tag)
    atm.uses(mainframe_banking_system, "Uses")
    customer.uses(atm, "Withdraws cash using")

    customer_service_staff = model.add_person(
        location=Location.Internal,
        name="Customer Service Staff",
        description="Customer service staff within the bank.",
        id="supportStaff",
    )
    customer_service_staff.tags.add(bank_staff_tag)
    customer_service_staff.uses(mainframe_banking_system, "Uses")
    customer.interacts_with(
        customer_service_staff, "Asks questions to", technology="Telephone"
    )

    back_office_staff = model.add_person(
        location=Location.Internal,
        name="Back Office Staff",
        description="Administration and support staff within the bank.",
        id="backoffice",
    )
    back_office_staff.tags.add(bank_staff_tag)
    back_office_staff.uses(mainframe_banking_system, "Uses")

    # containers
    single_page_application = internet_banking_system.add_container(
        "Single-Page Application",
        (
            "Provides all of the Internet banking functionality "
            "to customers via their web browser."
        ),
        "JavaScript and Angular",
        id="singlePageApplication",
    )
    single_page_application.tags.add(web_browser_tag)
    mobile_app = internet_banking_system.add_container(
        "Mobile App",
        "Provides a limited subset of the Internet banking functionality to customers via their mobile device.",
        "Xamarin",
        id="mobileApp",
    )
    mobile_app.tags.add(mobile_app_tag)
    web_application = internet_banking_system.add_container(
        "Web Application",
        "Delivers the static content and the Internet banking single page application.",
        "Java and Spring MVC",
        id="webApplication",
    )
    api_application = internet_banking_system.add_container(
        "API Application",
        "Provides Internet banking functionality via a JSON/HTTPS API.",
        "Java and Spring MVC",
        id="apiApplication",
    )
    database = internet_banking_system.add_container(
        "Database",
        "Stores user registration information, hashed authentication credentials, access logs, etc.",
        "Relational Database Schema",
        id="database",
    )
    database.tags.add(database_tag)

    customer.uses(web_application, "Uses", technology="HTTPS")
    customer.uses(single_page_application, "Uses")
    customer.uses(mobile_app, "Uses")
    web_application.uses(
        single_page_application, "Delivers to the customers web browser"
    )
    api_application.uses(database, "Reads from and writes to", technology="JDBC")
    api_application.uses(mainframe_banking_system, "Uses", technology="XML/HTTPS")
    api_application.uses(email_system, "Sends e-mail using", technology="SMTP")

    # components
    # - for a real-world software system, you would probably want to extract the components using
    # - static analysis/reflection rather than manually specifying them all

    signin_controller = api_application.add_component(
        name="Sign In Controller",
        description="Allows users to sign in to the Internet Banking System.",
        technology="Spring MVC Rest Controller",
        id="signinController",
    )
    accounts_summary_controller = api_application.add_component(
        name="Accounts Summary Controller",
        description="Provides customers with a summary of their bank accounts.",
        technology="Spring MVC Rest Controller",
        id="accountsSummaryController",
    )
    reset_password_controller = api_application.add_component(
        name="Reset Password Controller",
        description="Allows users to reset their passwords with a single use URL.",
        technology="Spring MVC Rest Controller",
        id="resetPasswordController",
    )
    security_component = api_application.add_component(
        name="Security Component",
        description="Provides functionality related to signing in, changing passwords, etc.",
        technology="Spring Bean",
        id="securityComponent",
    )
    mainframe_banking_systemFacade = api_application.add_component(
        name="Mainframe Banking System Facade",
        description="A facade onto the mainframe banking system.",
        technology="Spring Bean",
        id="mainframeBankingSystemFacade",
    )
    email_component = api_application.add_component(
        name="E-mail Component",
        description="Sends e-mails to users.",
        technology="Spring Bean",
        id="emailComponent",
    )

    for component in api_application.components:
        if component.technology == "Spring MVC Rest Controller":
            single_page_application.uses(component, "Makes API calls to", "JSON/HTTPS")
            mobile_app.uses(component, "Makes API calls to", "JSON/HTTPS")

    signin_controller.uses(security_component, "Uses")
    accounts_summary_controller.uses(mainframe_banking_systemFacade, "Uses")
    reset_password_controller.uses(security_component, "Uses")
    reset_password_controller.uses(email_component, "Uses")
    security_component.uses(database, "Reads from and writes to", "JDBC")
    mainframe_banking_systemFacade.uses(mainframe_banking_system, "Uses", "XML/HTTPS")
    email_component.uses(email_system, "Sends e-mail using")

    # TODO:!
    # model.AddImplicitRelationships()

    # deployment nodes and container instances
    developer_laptop = model.add_deployment_node(
        environment="Development",
        name="Developer Laptop",
        description="A developer laptop.",
        technology="Microsoft Windows 10 or Apple macOS",
    )
    apache_tomcat = developer_laptop.add_deployment_node(
        name="Docker - Web Server",
        description="A Docker container.",
        technology="Docker",
    ).add_deployment_node(
        name="Apache Tomcat",
        description="An open source Java EE web server.",
        technology="Apache Tomcat 8.x",
        instances=1,
        properties={"Xmx": "512M", "Xms": "1024M", "Java Version": "8"},
    )
    apache_tomcat.add_container(web_application)
    apache_tomcat.add_container(api_application)

    developer_laptop.add_deployment_node(
        "Docker - Database Server", "A Docker container.", "Docker"
    ).add_deployment_node(
        "Database Server", "A development database.", "Oracle 12c"
    ).add_container(
        database
    )

    developer_laptop.add_deployment_node(
        "Web Browser", "", "Chrome, Firefox, Safari, or Edge"
    ).add_container(single_page_application)

    customer_mobile_device = model.add_deployment_node(
        "Customer's mobile device", "", "Apple iOS or Android", environment="Live"
    )
    customer_mobile_device.add_container(mobile_app)

    customer_computer = model.add_deployment_node(
        "Customer's computer",
        "",
        "Microsoft Windows or Apple macOS",
        environment="Live",
    )
    customer_computer.add_deployment_node(
        "Web Browser", "", "Chrome, Firefox, Safari, or Edge"
    ).add_container(single_page_application)

    big_bank_data_center = model.add_deployment_node(
        "Big Bank plc", "", "Big Bank plc data center", environment="Live"
    )

    live_web_server = big_bank_data_center.add_deployment_node(
        "bigbank-web***",
        "A web server residing in the web server farm, accessed via F5 BIG-IP LTMs.",
        "Ubuntu 16.04 LTS",
        instances=4,
        properties={"Location": "London and Reading"},
    )
    live_web_server.add_deployment_node(
        "Apache Tomcat",
        "An open source Java EE web server.",
        "Apache Tomcat 8.x",
        instances=1,
        properties={"Xmx": "512M", "Xms": "1024M", "Java Version": "8"},
    ).add_container(web_application)

    live_api_server = big_bank_data_center.add_deployment_node(
        "bigbank-api***",
        "A web server residing in the web server farm, accessed via F5 BIG-IP LTMs.",
        "Ubuntu 16.04 LTS",
        instances=8,
        properties={"Location": "London and Reading"},
    )
    live_api_server.add_deployment_node(
        "Apache Tomcat",
        "An open source Java EE web server.",
        "Apache Tomcat 8.x",
        instances=1,
        properties={"Xmx": "512M", "Xms": "1024M", "Java Version": "8"},
    ).add_container(api_application)

    primary_database_server = big_bank_data_center.add_deployment_node(
        "bigbank-db01",
        "The primary database server.",
        "Ubuntu 16.04 LTS",
        instances=1,
        properties={"Location": "London"},
    ).add_deployment_node(
        "Oracle - Primary", "The primary, live database server.", "Oracle 12c"
    )
    primary_database_server.add_container(database)

    big_bank_db_02 = big_bank_data_center.add_deployment_node(
        "bigbank-db02",
        "The secondary database server.",
        "Ubuntu 16.04 LTS",
        instances=1,
        properties={"Location": "Reading"},
    )
    big_bank_db_02.tags.add(failover_tag)
    secondary_database_server = big_bank_db_02.add_deployment_node(
        "Oracle - Secondary",
        "A secondary, standby database server, used for failover purposes only.",
        "Oracle 12c",
    )
    secondary_database_server.tags.add(failover_tag)
    secondary_database = secondary_database_server.add_container(database)

    # # model.Relationships.Where(r=>r.Destination.Equals(secondary_database)).ToList().ForEach(r=>r.tags.add(failover_tag))
    # dataReplicationRelationship = primary_database_server.uses(
    #     secondary_database_server, "Replicates data to", ""
    # )
    # secondary_database.tags.add(failover_tag)

    # views/diagrams
    system_landscape_view = views.create_system_landscape_view(
        key="SystemLandscape",
        description="The system landscape diagram for Big Bank plc.",
    )
    system_landscape_view.add_all_elements()
    system_landscape_view.paper_size = PaperSize.A5_Landscape

    system_context_view = views.create_system_context_view(
        software_system=internet_banking_system,
        key="SystemContext",
        description="The system context diagram for the Internet Banking System.",
    )
    system_context_view.enterprise_boundary_visible = False
    system_context_view.add_nearest_neighbours(internet_banking_system)
    system_context_view.paper_size = PaperSize.A5_Landscape

    container_view = views.create_container_view(
        software_system=internet_banking_system,
        key="Containers",
        description="The container diagram for the Internet Banking System.",
    )
    container_view.add(customer)
    container_view.add_all_containers()
    container_view.add(mainframe_banking_system)
    container_view.add(email_system)
    container_view.paper_size = PaperSize.A5_Landscape

    component_view = views.create_component_view(
        container=api_application,
        key="Components",
        description="The component diagram for the API Application.",
    )
    component_view.add(mobile_app)
    component_view.add(single_page_application)
    component_view.add(database)
    component_view.add_all_components()
    component_view.add(mainframe_banking_system)
    component_view.add(email_system)
    component_view.paper_size = PaperSize.A5_Landscape

    # systemLandscapeView.AddAnimation(internet_banking_system, customer, mainframe_banking_system, emailSystem)
    # systemLandscapeView.AddAnimation(atm)
    # systemLandscapeView.AddAnimation(customerServiceStaff, back_office_staff)

    # systemContextView.AddAnimation(internet_banking_system)
    # systemContextView.AddAnimation(customer)
    # systemContextView.AddAnimation(mainframe_banking_system)
    # systemContextView.AddAnimation(emailSystem)

    # containerView.AddAnimation(customer, mainframe_banking_system, emailSystem)
    # containerView.AddAnimation(webApplication)
    # containerView.AddAnimation(singlePageApplication)
    # containerView.AddAnimation(mobile_app)
    # containerView.AddAnimation(apiApplication)
    # containerView.AddAnimation(database)

    # componentView.AddAnimation(singlePageApplication, mobile_app)
    # componentView.AddAnimation(signinController, securityComponent, database)
    # componentView.AddAnimation(accountsSummaryController, mainframe_banking_systemFacade, mainframe_banking_system)
    # componentView.AddAnimation(resetPasswordController, emailComponent, database)

    # # dynamic diagrams and deployment diagrams are not available with the Free Plan
    # DynamicView dynamicView = views.CreateDynamicView(apiApplication, "SignIn", "Summarises how the sign in feature works in the single-page application.")
    # dynamicView.Add(singlePageApplication, "Submits credentials to", signinController)
    # dynamicView.Add(signinController, "Calls isAuthenticated() on", securityComponent)
    # dynamicView.Add(securityComponent, "select * from users where username = ?", database)
    # dynamicView.PaperSize = PaperSize.A5_Landscape

    # DeploymentView developmentDeploymentView = views.CreateDeploymentView(internet_banking_system, "DevelopmentDeployment", "An example development deployment scenario for the Internet Banking System.")
    # developmentDeploymentView.Environment = "Development"
    # developmentDeploymentView.Add(developerLaptop)
    # developmentDeploymentView.PaperSize = PaperSize.A5_Landscape

    # DeploymentView liveDeploymentView = views.CreateDeploymentView(internet_banking_system, "LiveDeployment", "An example live deployment scenario for the Internet Banking System.")
    # liveDeploymentView.Environment = "Live"
    # liveDeploymentView.Add(big_bank_data_center)
    # liveDeploymentView.Add(customerMobileDevice)
    # liveDeploymentView.Add(customerComputer)
    # liveDeploymentView.Add(dataReplicationRelationship)
    # liveDeploymentView.PaperSize = PaperSize.A5_Landscape

    # colours, shapes and other diagram styling
    styles = views.configuration.styles
    styles.add(
        ElementStyle(tag=Tags.SOFTWARE_SYSTEM, background="#1168bd", color="#ffffff")
    )
    styles.add(ElementStyle(tag=Tags.CONTAINER, background="#438dd5", color="#ffffff"))
    styles.add(ElementStyle(tag=Tags.COMPONENT, background="#85bbf0", color="#000000"))
    styles.add(
        ElementStyle(
            tag=Tags.PERSON,
            background="#08427b",
            color="#ffffff",
            shape=Shape.Person,
            font_size=22,
        )
    )
    styles.add(
        ElementStyle(tag=existing_system_tag, background="#999999", color="#ffffff")
    )
    styles.add(ElementStyle(tag=bank_staff_tag, background="#999999", color="#ffffff"))
    styles.add(ElementStyle(tag=web_browser_tag, shape=Shape.WebBrowser))
    styles.add(ElementStyle(tag=mobile_app_tag, shape=Shape.MobileDeviceLandscape))
    styles.add(ElementStyle(tag=database_tag, shape=Shape.Cylinder))
    styles.add(ElementStyle(tag=failover_tag, opacity=25))
    styles.add(RelationshipStyle(tag=failover_tag, opacity=25, position=70))

    return workspace