diff --git a/Structurizr.ActiveDirectory/Structurizr.ActiveDirectory.csproj b/Structurizr.ActiveDirectory/Structurizr.ActiveDirectory.csproj index 1d7b98d..581ff58 100644 --- a/Structurizr.ActiveDirectory/Structurizr.ActiveDirectory.csproj +++ b/Structurizr.ActiveDirectory/Structurizr.ActiveDirectory.csproj @@ -8,12 +8,12 @@ Exe - netcoreapp1.1 + netcoreapp2.1 - + \ No newline at end of file diff --git a/Structurizr.AdrTools.Tests/AdrTools/AdrToolsImporterTests.cs b/Structurizr.AdrTools.Tests/AdrTools/AdrToolsImporterTests.cs index b0122bf..f3849c7 100644 --- a/Structurizr.AdrTools.Tests/AdrTools/AdrToolsImporterTests.cs +++ b/Structurizr.AdrTools.Tests/AdrTools/AdrToolsImporterTests.cs @@ -106,7 +106,8 @@ public void Test_ImportArchitectureDecisionRecords_RewritesLinksBetweenADRsWhenT importer.ImportArchitectureDecisionRecords(); Decision decision5 = _documentation.Decisions.Where(d => d.Id == "5").First(); - Assert.True(decision5.Content.Contains("Amended by [9. Help scripts](#/:9)")); + // sync with java impl %2F instead of / + Assert.True(decision5.Content.Contains("Amended by [9. Help scripts](#%2F:9)")); } [Fact] @@ -127,7 +128,8 @@ public void Test_ImportArchitectureDecisionRecords_SupportsTheIncorrectSpellingO Decision decision4 = _documentation.Decisions.Where(d => d.Id == "4").First(); Assert.Equal(DecisionStatus.Superseded, decision4.Status); - Assert.True(decision4.Content.Contains("Superceded by [10. AsciiDoc format](#/:10)")); + // sync with java impl %2F instead of / + Assert.True(decision4.Content.Contains("Superceded by [10. AsciiDoc format](#%2F:10)")); } } diff --git a/Structurizr.AdrTools.Tests/Structurizr.AdrTools.Tests.csproj b/Structurizr.AdrTools.Tests/Structurizr.AdrTools.Tests.csproj index 9f13f19..e939dbb 100644 --- a/Structurizr.AdrTools.Tests/Structurizr.AdrTools.Tests.csproj +++ b/Structurizr.AdrTools.Tests/Structurizr.AdrTools.Tests.csproj @@ -1,6 +1,6 @@ - netcoreapp1.1 + netcoreapp2.1 false diff --git a/Structurizr.AdrTools/AdrTools/AdrToolsImporter.cs b/Structurizr.AdrTools/AdrTools/AdrToolsImporter.cs index 0bfa2db..81676fb 100644 --- a/Structurizr.AdrTools/AdrTools/AdrToolsImporter.cs +++ b/Structurizr.AdrTools/AdrTools/AdrToolsImporter.cs @@ -90,11 +90,15 @@ public ISet ImportArchitectureDecisionRecords(SoftwareSystem softwareS private string CalculateUrl(SoftwareSystem softwareSystem, string id) { if (softwareSystem == null) { - return "#/:" + UrlEncode(id); + // sync with java impl + return "#" + UrlEncode("/") + ":" + UrlEncode(id); } else { - return "#" + UrlEncode(softwareSystem.CanonicalName) + ":" + UrlEncode(id); + //CanonicalName impl changed: old "/", new "SoftwareSystem://" (CanonicalNameGenerator.SoftwareSystemType) + var name = softwareSystem.CanonicalName; + name = name.Substring("SoftwareSystem:/".Length); // last "/" is reused + return "#" + UrlEncode(name) + ":" + UrlEncode(id); } } diff --git a/Structurizr.AdrTools/Structurizr.AdrTools.csproj b/Structurizr.AdrTools/Structurizr.AdrTools.csproj index 38c9414..c9c8733 100644 --- a/Structurizr.AdrTools/Structurizr.AdrTools.csproj +++ b/Structurizr.AdrTools/Structurizr.AdrTools.csproj @@ -7,11 +7,11 @@ - netstandard1.3;net45 + netstandard2.0;net45 - + diff --git a/Structurizr.Analysis/Structurizr.Analysis.csproj b/Structurizr.Analysis/Structurizr.Analysis.csproj index 3bfbc40..f536c8b 100644 --- a/Structurizr.Analysis/Structurizr.Analysis.csproj +++ b/Structurizr.Analysis/Structurizr.Analysis.csproj @@ -7,11 +7,11 @@ - netstandard1.3;net45 + netstandard2.0;net45 - + \ No newline at end of file diff --git a/Structurizr.Annotations/Structurizr.Annotations.csproj b/Structurizr.Annotations/Structurizr.Annotations.csproj index 2395b0c..b0aab59 100644 --- a/Structurizr.Annotations/Structurizr.Annotations.csproj +++ b/Structurizr.Annotations/Structurizr.Annotations.csproj @@ -17,7 +17,12 @@ + + netstandard2.0;net20 bin\$(Configuration)\$(TargetFramework)\Structurizr.Annotations.xml 1.6.0 @@ -48,4 +53,11 @@ 4.1.0 + + + + 4.1.0 + + + diff --git a/Structurizr.Cecil.Examples/Structurizr.Cecil.Examples.csproj b/Structurizr.Cecil.Examples/Structurizr.Cecil.Examples.csproj index 9d7a008..2a94992 100644 --- a/Structurizr.Cecil.Examples/Structurizr.Cecil.Examples.csproj +++ b/Structurizr.Cecil.Examples/Structurizr.Cecil.Examples.csproj @@ -8,7 +8,7 @@ - + diff --git a/Structurizr.Cecil/Structurizr.Cecil.csproj b/Structurizr.Cecil/Structurizr.Cecil.csproj index ffa5ec8..4a98d97 100644 --- a/Structurizr.Cecil/Structurizr.Cecil.csproj +++ b/Structurizr.Cecil/Structurizr.Cecil.csproj @@ -16,12 +16,12 @@ - netstandard1.3;net45 + netstandard2.0;net45 - + diff --git a/Structurizr.Examples/BigBankPlc.cs b/Structurizr.Examples/BigBankPlc.cs index 005e661..b9e7d32 100644 --- a/Structurizr.Examples/BigBankPlc.cs +++ b/Structurizr.Examples/BigBankPlc.cs @@ -5,7 +5,7 @@ namespace Structurizr.Examples { - + /// /// This is an example workspace to illustrate the key features of Structurizr, /// based around a fictional Internet Banking System for Big Bank plc. @@ -28,238 +28,270 @@ public class BigBankPlc public static Workspace Create() { - Workspace workspace = new Workspace("Big Bank plc", "This is an example workspace to illustrate the key features of Structurizr, based around a fictional online banking system."); - Model model = workspace.Model; - ViewSet views = workspace.Views; - - model.Enterprise = new Enterprise("Big Bank plc"); - - // people and software systems - Person customer = model.AddPerson(Location.External, "Personal Banking Customer", "A customer of the bank, with personal bank accounts."); - - SoftwareSystem internetBankingSystem = model.AddSoftwareSystem(Location.Internal, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments."); - customer.Uses(internetBankingSystem, "Uses"); - - SoftwareSystem mainframeBankingSystem = model.AddSoftwareSystem(Location.Internal, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc."); - mainframeBankingSystem.AddTags(ExistingSystemTag); - internetBankingSystem.Uses(mainframeBankingSystem, "Uses"); - - SoftwareSystem emailSystem = model.AddSoftwareSystem(Location.Internal, "E-mail System", "The internal Microsoft Exchange e-mail system."); - internetBankingSystem.Uses(emailSystem, "Sends e-mail using"); - emailSystem.AddTags(ExistingSystemTag); - emailSystem.Delivers(customer, "Sends e-mails to"); - - SoftwareSystem atm = model.AddSoftwareSystem(Location.Internal, "ATM", "Allows customers to withdraw cash."); - atm.AddTags(ExistingSystemTag); - atm.Uses(mainframeBankingSystem, "Uses"); - customer.Uses(atm, "Withdraws cash using"); - - Person customerServiceStaff = model.AddPerson(Location.Internal, "Customer Service Staff", "Customer service staff within the bank."); - customerServiceStaff.AddTags(BankStaffTag); - customerServiceStaff.Uses(mainframeBankingSystem, "Uses"); - customer.InteractsWith(customerServiceStaff, "Asks questions to", "Telephone"); - - Person backOfficeStaff = model.AddPerson(Location.Internal, "Back Office Staff", "Administration and support staff within the bank."); - backOfficeStaff.AddTags(BankStaffTag); - backOfficeStaff.Uses(mainframeBankingSystem, "Uses"); - - // containers - Container singlePageApplication = internetBankingSystem.AddContainer("Single-Page Application", "Provides all of the Internet banking functionality to customers via their web browser.", "JavaScript and Angular"); - singlePageApplication.AddTags(WebBrowserTag); - Container mobileApp = internetBankingSystem.AddContainer("Mobile App", "Provides a limited subset of the Internet banking functionality to customers via their mobile device.", "Xamarin"); - mobileApp.AddTags(MobileAppTag); - Container webApplication = internetBankingSystem.AddContainer("Web Application", "Delivers the static content and the Internet banking single page application.", "Java and Spring MVC"); - Container apiApplication = internetBankingSystem.AddContainer("API Application", "Provides Internet banking functionality via a JSON/HTTPS API.", "Java and Spring MVC"); - Container database = internetBankingSystem.AddContainer("Database", "Stores user registration information, hashed authentication credentials, access logs, etc.", "Relational Database Schema"); - database.AddTags(DatabaseTag); - - customer.Uses(webApplication, "Uses", "HTTPS"); - customer.Uses(singlePageApplication, "Uses", ""); - customer.Uses(mobileApp, "Uses", ""); - webApplication.Uses(singlePageApplication, "Delivers to the customer's web browser", ""); - apiApplication.Uses(database, "Reads from and writes to", "JDBC"); - apiApplication.Uses(mainframeBankingSystem, "Uses", "XML/HTTPS"); - apiApplication.Uses(emailSystem, "Sends e-mail using", "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 - Component signinController = apiApplication.AddComponent("Sign In Controller", "Allows users to sign in to the Internet Banking System.", "Spring MVC Rest Controller"); - Component accountsSummaryController = apiApplication.AddComponent("Accounts Summary Controller", "Provides customers with a summary of their bank accounts.", "Spring MVC Rest Controller"); - Component resetPasswordController = apiApplication.AddComponent("Reset Password Controller", "Allows users to reset their passwords with a single use URL.", "Spring MVC Rest Controller"); - Component securityComponent = apiApplication.AddComponent("Security Component", "Provides functionality related to signing in, changing passwords, etc.", "Spring Bean"); - Component mainframeBankingSystemFacade = apiApplication.AddComponent("Mainframe Banking System Facade", "A facade onto the mainframe banking system.", "Spring Bean"); - Component emailComponent = apiApplication.AddComponent("E-mail Component", "Sends e-mails to users.", "Spring Bean"); - - apiApplication.Components.Where(c => "Spring MVC Rest Controller".Equals(c.Technology)).ToList().ForEach(c => singlePageApplication.Uses(c, "Makes API calls to", "JSON/HTTPS")); - apiApplication.Components.Where(c => "Spring MVC Rest Controller".Equals(c.Technology)).ToList().ForEach(c => mobileApp.Uses(c, "Makes API calls to", "JSON/HTTPS")); - signinController.Uses(securityComponent, "Uses"); - accountsSummaryController.Uses(mainframeBankingSystemFacade, "Uses"); - resetPasswordController.Uses(securityComponent, "Uses"); - resetPasswordController.Uses(emailComponent, "Uses"); - securityComponent.Uses(database, "Reads from and writes to", "JDBC"); - mainframeBankingSystemFacade.Uses(mainframeBankingSystem, "Uses", "XML/HTTPS"); - emailComponent.Uses(emailSystem, "Sends e-mail using"); - - model.AddImplicitRelationships(); - - // deployment nodes and container instances - DeploymentNode developerLaptop = model.AddDeploymentNode("Development", "Developer Laptop", "A developer laptop.", "Microsoft Windows 10 or Apple macOS"); - DeploymentNode apacheTomcat = developerLaptop.AddDeploymentNode("Docker Container - Web Server", "A Docker container.", "Docker") - .AddDeploymentNode("Apache Tomcat", "An open source Java EE web server.", "Apache Tomcat 8.x", 1, DictionaryUtils.Create("Xmx=512M", "Xms=1024M", "Java Version=8")); - apacheTomcat.Add(webApplication); - apacheTomcat.Add(apiApplication); - - developerLaptop.AddDeploymentNode("Docker Container - Database Server", "A Docker container.", "Docker") - .AddDeploymentNode("Database Server", "A development database.", "Oracle 12c") - .Add(database); - - developerLaptop.AddDeploymentNode("Web Browser", "", "Google Chrome, Mozilla Firefox, Apple Safari or Microsoft Edge").Add(singlePageApplication); - - DeploymentNode customerMobileDevice = model.AddDeploymentNode("Live", "Customer's mobile device", "", "Apple iOS or Android"); - customerMobileDevice.Add(mobileApp); - - DeploymentNode customerComputer = model.AddDeploymentNode("Live", "Customer's computer", "", "Microsoft Windows or Apple macOS"); - customerComputer.AddDeploymentNode("Web Browser", "", "Google Chrome, Mozilla Firefox, Apple Safari or Microsoft Edge").Add(singlePageApplication); - - DeploymentNode bigBankDataCenter = model.AddDeploymentNode("Live", "Big Bank plc", "", "Big Bank plc data center"); - - DeploymentNode liveWebServer = bigBankDataCenter.AddDeploymentNode("bigbank-web***", "A web server residing in the web server farm, accessed via F5 BIG-IP LTMs.", "Ubuntu 16.04 LTS", 4, DictionaryUtils.Create("Location=London and Reading")); - liveWebServer.AddDeploymentNode("Apache Tomcat", "An open source Java EE web server.", "Apache Tomcat 8.x", 1, DictionaryUtils.Create("Xmx=512M", "Xms=1024M", "Java Version=8")) - .Add(webApplication); - - DeploymentNode liveApiServer = bigBankDataCenter.AddDeploymentNode("bigbank-api***", "A web server residing in the web server farm, accessed via F5 BIG-IP LTMs.", "Ubuntu 16.04 LTS", 8, DictionaryUtils.Create("Location=London and Reading")); - liveApiServer.AddDeploymentNode("Apache Tomcat", "An open source Java EE web server.", "Apache Tomcat 8.x", 1, DictionaryUtils.Create("Xmx=512M", "Xms=1024M", "Java Version=8")) - .Add(apiApplication); - - DeploymentNode primaryDatabaseServer = bigBankDataCenter.AddDeploymentNode("bigbank-db01", "The primary database server.", "Ubuntu 16.04 LTS", 1, DictionaryUtils.Create("Location=London")) - .AddDeploymentNode("Oracle - Primary", "The primary, live database server.", "Oracle 12c"); - primaryDatabaseServer.Add(database); - - DeploymentNode secondaryDatabaseServer = bigBankDataCenter.AddDeploymentNode("bigbank-db02", "The secondary database server.", "Ubuntu 16.04 LTS", 1, DictionaryUtils.Create("Location=Reading")) - .AddDeploymentNode("Oracle - Secondary", "A secondary, standby database server, used for failover purposes only.", "Oracle 12c"); - ContainerInstance secondaryDatabase = secondaryDatabaseServer.Add(database); - - model.Relationships.Where(r=>r.Destination.Equals(secondaryDatabase)).ToList().ForEach(r=>r.AddTags(FailoverTag)); - Relationship dataReplicationRelationship = primaryDatabaseServer.Uses(secondaryDatabaseServer, "Replicates data to", ""); - secondaryDatabase.AddTags(FailoverTag); - - // views/diagrams - SystemLandscapeView systemLandscapeView = views.CreateSystemLandscapeView("SystemLandscape", "The system landscape diagram for Big Bank plc."); - systemLandscapeView.AddAllElements(); - systemLandscapeView.PaperSize = PaperSize.A5_Landscape; - - SystemContextView systemContextView = views.CreateSystemContextView(internetBankingSystem, "SystemContext", "The system context diagram for the Internet Banking System."); - systemContextView.EnterpriseBoundaryVisible = false; - systemContextView.AddNearestNeighbours(internetBankingSystem); - systemContextView.PaperSize = PaperSize.A5_Landscape; - - ContainerView containerView = views.CreateContainerView(internetBankingSystem, "Containers", "The container diagram for the Internet Banking System."); - containerView.Add(customer); - containerView.AddAllContainers(); - containerView.Add(mainframeBankingSystem); - containerView.Add(emailSystem); - containerView.PaperSize = PaperSize.A5_Landscape; - - ComponentView componentView = views.CreateComponentView(apiApplication, "Components", "The component diagram for the API Application."); - componentView.Add(mobileApp); - componentView.Add(singlePageApplication); - componentView.Add(database); - componentView.AddAllComponents(); - componentView.Add(mainframeBankingSystem); - componentView.Add(emailSystem); - componentView.PaperSize = PaperSize.A5_Landscape; - - systemLandscapeView.AddAnimation(internetBankingSystem, customer, mainframeBankingSystem, emailSystem); - systemLandscapeView.AddAnimation(atm); - systemLandscapeView.AddAnimation(customerServiceStaff, backOfficeStaff); - - systemContextView.AddAnimation(internetBankingSystem); - systemContextView.AddAnimation(customer); - systemContextView.AddAnimation(mainframeBankingSystem); - systemContextView.AddAnimation(emailSystem); - - containerView.AddAnimation(customer, mainframeBankingSystem, emailSystem); - containerView.AddAnimation(webApplication); - containerView.AddAnimation(singlePageApplication); - containerView.AddAnimation(mobileApp); - containerView.AddAnimation(apiApplication); - containerView.AddAnimation(database); - - componentView.AddAnimation(singlePageApplication, mobileApp); - componentView.AddAnimation(signinController, securityComponent, database); - componentView.AddAnimation(accountsSummaryController, mainframeBankingSystemFacade, mainframeBankingSystem); - 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(internetBankingSystem, "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(internetBankingSystem, "LiveDeployment", "An example live deployment scenario for the Internet Banking System."); - liveDeploymentView.Environment = "Live"; - liveDeploymentView.Add(bigBankDataCenter); - liveDeploymentView.Add(customerMobileDevice); - liveDeploymentView.Add(customerComputer); - liveDeploymentView.Add(dataReplicationRelationship); - liveDeploymentView.PaperSize = PaperSize.A5_Landscape; - - // colours, shapes and other diagram styling - Styles styles = views.Configuration.Styles; - styles.Add(new ElementStyle(Tags.Element) { Color = "#ffffff" }); - styles.Add(new ElementStyle(Tags.SoftwareSystem) { Background = "#1168bd" }); - styles.Add(new ElementStyle(Tags.Container) { Background = "#438dd5" }); - styles.Add(new ElementStyle(Tags.Component) { Background = "#85bbf0", Color = "#000000" }); - styles.Add(new ElementStyle(Tags.Person) { Background = "#08427b", Shape = Shape.Person, FontSize = 22}); - styles.Add(new ElementStyle(ExistingSystemTag) { Background = "#999999"}); - styles.Add(new ElementStyle(BankStaffTag) { Background = "#999999" }); - styles.Add(new ElementStyle(WebBrowserTag) { Shape = Shape.WebBrowser }); - styles.Add(new ElementStyle(MobileAppTag) { Shape = Shape.MobileDeviceLandscape }); - styles.Add(new ElementStyle(DatabaseTag) { Shape = Shape.Cylinder }); - styles.Add(new ElementStyle(FailoverTag) { Opacity = 25 }); - styles.Add(new RelationshipStyle(FailoverTag) { Opacity = 25, Position = 70}); - - // documentation - // - usually the documentation would be included from separate Markdown/AsciiDoc files, but this is just an example - StructurizrDocumentationTemplate template = new StructurizrDocumentationTemplate(workspace); - template.AddContextSection(internetBankingSystem, Format.Markdown, - "Here is some context about the Internet Banking System...\n" + - "![](embed:SystemLandscape)\n" + - "![](embed:SystemContext)\n" + - "### Internet Banking System\n...\n" + - "### Mainframe Banking System\n...\n"); - template.AddContainersSection(internetBankingSystem, Format.Markdown, - "Here is some information about the containers within the Internet Banking System...\n" + - "![](embed:Containers)\n" + - "### Web Application\n...\n" + - "### Database\n...\n"); - template.AddComponentsSection(webApplication, Format.Markdown, - "Here is some information about the API Application...\n" + - "![](embed:Components)\n" + - "### Sign in process\n" + - "Here is some information about the Sign In Controller, including how the sign in process works...\n" + - "![](embed:SignIn)"); - template.AddDevelopmentEnvironmentSection(internetBankingSystem, Format.AsciiDoc, - "Here is some information about how to set up a development environment for the Internet Banking System...\n" + - "image::embed:DevelopmentDeployment[]"); - template.AddDeploymentSection(internetBankingSystem, Format.AsciiDoc, - "Here is some information about the live deployment environment for the Internet Banking System...\n" + - "image::embed:LiveDeployment[]"); - - return workspace; + Workspace workspace = new Workspace("Big Bank plc", "This is an example workspace to illustrate the key features of Structurizr, based around a fictional online banking system."); + Model model = workspace.Model; + model.ImpliedRelationshipsStrategy = new CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy(); + ViewSet views = workspace.Views; + + model.Enterprise = new Enterprise("Big Bank plc"); + + // people and software systems + SoftwareSystem internetBankingSystem = model.AddSoftwareSystem(Location.Internal, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments."); + Person customer = model.AddPerson(Location.External, "Personal Banking Customer", "A customer of the bank, with personal bank accounts."); + + customer.Uses(internetBankingSystem, "Views account balances, and makes payments using"); + + SoftwareSystem mainframeBankingSystem = model.AddSoftwareSystem(Location.Internal, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc."); + mainframeBankingSystem.AddTags(ExistingSystemTag); + internetBankingSystem.Uses(mainframeBankingSystem, "Gets account information from, and makes payments using"); + + SoftwareSystem emailSystem = model.AddSoftwareSystem(Location.Internal, "E-mail System", "The internal Microsoft Exchange e-mail system."); + internetBankingSystem.Uses(emailSystem, "Sends e-mail using"); + emailSystem.AddTags(ExistingSystemTag); + emailSystem.Delivers(customer, "Sends e-mails to"); + + SoftwareSystem atm = model.AddSoftwareSystem(Location.Internal, "ATM", "Allows customers to withdraw cash."); + atm.AddTags(ExistingSystemTag); + atm.Uses(mainframeBankingSystem, "Uses"); + customer.Uses(atm, "Withdraws cash using"); + + Person customerServiceStaff = model.AddPerson(Location.Internal, "Customer Service Staff", "Customer service staff within the bank."); + customerServiceStaff.AddTags(BankStaffTag); + customerServiceStaff.Uses(mainframeBankingSystem, "Uses"); + customer.InteractsWith(customerServiceStaff, "Asks questions to", "Telephone"); + + Person backOfficeStaff = model.AddPerson(Location.Internal, "Back Office Staff", "Administration and support staff within the bank."); + backOfficeStaff.AddTags(BankStaffTag); + backOfficeStaff.Uses(mainframeBankingSystem, "Uses"); + + // containers + Container singlePageApplication = internetBankingSystem.AddContainer("Single-Page Application", "Provides all of the Internet banking functionality to customers via their web browser.", "JavaScript and Angular"); + singlePageApplication.AddTags(WebBrowserTag); + Container mobileApp = internetBankingSystem.AddContainer("Mobile App", "Provides a limited subset of the Internet banking functionality to customers via their mobile device.", "Xamarin"); + mobileApp.AddTags(MobileAppTag); + Container webApplication = internetBankingSystem.AddContainer("Web Application", "Delivers the static content and the Internet banking single page application.", "Java and Spring MVC"); + Container apiApplication = internetBankingSystem.AddContainer("API Application", "Provides Internet banking functionality via a JSON/HTTPS API.", "Java and Spring MVC"); + Container database = internetBankingSystem.AddContainer("Database", "Stores user registration information, hashed authentication credentials, access logs, etc.", "Relational Database Schema"); + database.AddTags(DatabaseTag); + + customer.Uses(webApplication, "Visits bigbank.com/ib using", "HTTPS"); + customer.Uses(singlePageApplication, "Views account balances, and makes payments using", ""); + customer.Uses(mobileApp, "Views account balances, and makes payments using", ""); + webApplication.Uses(singlePageApplication, "Delivers to the customer's web browser", ""); + apiApplication.Uses(database, "Reads from and writes to", "JDBC"); + apiApplication.Uses(mainframeBankingSystem, "Makes API calls to", "XML/HTTPS"); + apiApplication.Uses(emailSystem, "Sends e-mail using", "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 + Component signinController = apiApplication.AddComponent("Sign In Controller", "Allows users to sign in to the Internet Banking System.", "Spring MVC Rest Controller"); + Component accountsSummaryController = apiApplication.AddComponent("Accounts Summary Controller", "Provides customers with a summary of their bank accounts.", "Spring MVC Rest Controller"); + Component resetPasswordController = apiApplication.AddComponent("Reset Password Controller", "Allows users to reset their passwords with a single use URL.", "Spring MVC Rest Controller"); + Component securityComponent = apiApplication.AddComponent("Security Component", "Provides functionality related to signing in, changing passwords, etc.", "Spring Bean"); + Component mainframeBankingSystemFacade = apiApplication.AddComponent("Mainframe Banking System Facade", "A facade onto the mainframe banking system.", "Spring Bean"); + Component emailComponent = apiApplication.AddComponent("E-mail Component", "Sends e-mails to users.", "Spring Bean"); + + apiApplication.Components.Where(c => "Spring MVC Rest Controller".Equals(c.Technology)).ToList().ForEach(c => singlePageApplication.Uses(c, "Makes API calls to", "JSON/HTTPS")); + apiApplication.Components.Where(c => "Spring MVC Rest Controller".Equals(c.Technology)).ToList().ForEach(c => mobileApp.Uses(c, "Makes API calls to", "JSON/HTTPS")); + signinController.Uses(securityComponent, "Uses"); + accountsSummaryController.Uses(mainframeBankingSystemFacade, "Uses"); + resetPasswordController.Uses(securityComponent, "Uses"); + resetPasswordController.Uses(emailComponent, "Uses"); + securityComponent.Uses(database, "Reads from and writes to", "JDBC"); + mainframeBankingSystemFacade.Uses(mainframeBankingSystem, "Uses", "XML/HTTPS"); + emailComponent.Uses(emailSystem, "Sends e-mail using"); + + // deployment nodes and container instances + DeploymentNode developerLaptop = model.AddDeploymentNode("Development", "Developer Laptop", "A developer laptop.", "Microsoft Windows 10 or Apple macOS"); + DeploymentNode apacheTomcat = developerLaptop + .AddDeploymentNode("Docker Container - Web Server", "A Docker container.", "Docker") + .AddDeploymentNode("Apache Tomcat", "An open source Java EE web server.", "Apache Tomcat 8.x", 1, DictionaryUtils.Create("Xmx=512M", "Xms=1024M", "Java Version=8")); + ContainerInstance developmentWebApplication = apacheTomcat.Add(webApplication); + ContainerInstance developmentApiApplication = apacheTomcat.Add(apiApplication); + + DeploymentNode bigBankDataCenterForDevelopment = model.AddDeploymentNode("Development", "Big Bank plc", "", "Big Bank plc data center"); + SoftwareSystemInstance developmentMainframeBankingSystem = bigBankDataCenterForDevelopment + .AddDeploymentNode("bigbank-dev001").Add(mainframeBankingSystem); + + ContainerInstance developmentDatabase = developerLaptop + .AddDeploymentNode("Docker Container - Database Server", "A Docker container.", "Docker") + .AddDeploymentNode("Database Server", "A development database.", "Oracle 12c") + .Add(database); + + ContainerInstance developmentSinglePageApplication = developerLaptop + .AddDeploymentNode("Web Browser", "", "Chrome, Firefox, Safari, or Edge") + .Add(singlePageApplication); + + DeploymentNode customerMobileDevice = model.AddDeploymentNode("Live", "Customer's mobile device", "", "Apple iOS or Android"); + ContainerInstance liveMobileApp = customerMobileDevice.Add(mobileApp); + + DeploymentNode customerComputer = model.AddDeploymentNode("Live", "Customer's computer", "", "Microsoft Windows or Apple macOS"); + ContainerInstance liveSinglePageApplication = customerComputer + .AddDeploymentNode("Web Browser", "", "Chrome, Firefox, Safari, or Edge") + .Add(singlePageApplication); + + DeploymentNode bigBankDataCenterForLive = + model.AddDeploymentNode("Live", "Big Bank plc", "", "Big Bank plc data center"); + SoftwareSystemInstance liveMainframeBankingSystem = bigBankDataCenterForLive + .AddDeploymentNode("bigbank-prod001").Add(mainframeBankingSystem); + + DeploymentNode liveWebServer = bigBankDataCenterForLive.AddDeploymentNode("bigbank-web***", "A web server residing in the web server farm, accessed via F5 BIG-IP LTMs.", "Ubuntu 16.04 LTS", 4, DictionaryUtils.Create("Location=London and Reading")); + ContainerInstance liveWebApplication = liveWebServer.AddDeploymentNode("Apache Tomcat", "An open source Java EE web server.", "Apache Tomcat 8.x", 1, DictionaryUtils.Create("Xmx=512M", "Xms=1024M", "Java Version=8")) + .Add(webApplication); + + DeploymentNode liveApiServer = bigBankDataCenterForLive.AddDeploymentNode("bigbank-api***", "A web server residing in the web server farm, accessed via F5 BIG-IP LTMs.", "Ubuntu 16.04 LTS", 8, DictionaryUtils.Create("Location=London and Reading")); + ContainerInstance liveApiApplication = liveApiServer.AddDeploymentNode("Apache Tomcat", "An open source Java EE web server.", "Apache Tomcat 8.x", 1, DictionaryUtils.Create("Xmx=512M", "Xms=1024M", "Java Version=8")) + .Add(apiApplication); + + DeploymentNode primaryDatabaseServer = bigBankDataCenterForLive + .AddDeploymentNode("bigbank-db01", "The primary database server.", "Ubuntu 16.04 LTS", 1, DictionaryUtils.Create("Location=London")) + .AddDeploymentNode("Oracle - Primary", "The primary, live database server.", "Oracle 12c"); + ContainerInstance livePrimaryDatabase = primaryDatabaseServer.Add(database); + + DeploymentNode bigBankdb02 = bigBankDataCenterForLive.AddDeploymentNode("bigbank-db02", "The secondary database server.", "Ubuntu 16.04 LTS", 1, DictionaryUtils.Create("Location=Reading")); + bigBankdb02.AddTags(FailoverTag); + DeploymentNode secondaryDatabaseServer = bigBankdb02.AddDeploymentNode("Oracle - Secondary", "A secondary, standby database server, used for failover purposes only.", "Oracle 12c"); + secondaryDatabaseServer.AddTags(FailoverTag); + ContainerInstance liveSecondaryDatabase = secondaryDatabaseServer.Add(database); + + model.Relationships.Where(r => r.Destination.Equals(liveSecondaryDatabase)).ToList().ForEach(r => r.AddTags(FailoverTag)); + Relationship dataReplicationRelationship = primaryDatabaseServer.Uses(secondaryDatabaseServer, "Replicates data to", ""); + liveSecondaryDatabase.AddTags(FailoverTag); + + // views/diagrams + SystemLandscapeView systemLandscapeView = views.CreateSystemLandscapeView("SystemLandscape", "The system landscape diagram for Big Bank plc."); + systemLandscapeView.AddAllElements(); + systemLandscapeView.PaperSize = PaperSize.A5_Landscape; + + SystemContextView systemContextView = views.CreateSystemContextView(internetBankingSystem, "SystemContext", "The system context diagram for the Internet Banking System."); + systemContextView.EnterpriseBoundaryVisible = false; + systemContextView.AddNearestNeighbours(internetBankingSystem); + systemContextView.PaperSize = PaperSize.A5_Landscape; + + ContainerView containerView = views.CreateContainerView(internetBankingSystem, "Containers", "The container diagram for the Internet Banking System."); + containerView.Add(customer); + containerView.AddAllContainers(); + containerView.Add(mainframeBankingSystem); + containerView.Add(emailSystem); + containerView.PaperSize = PaperSize.A5_Landscape; + + ComponentView componentView = views.CreateComponentView(apiApplication, "Components", "The component diagram for the API Application."); + componentView.Add(mobileApp); + componentView.Add(singlePageApplication); + componentView.Add(database); + componentView.AddAllComponents(); + componentView.Add(mainframeBankingSystem); + componentView.Add(emailSystem); + componentView.PaperSize = PaperSize.A5_Landscape; + + systemLandscapeView.AddAnimation(internetBankingSystem, customer, mainframeBankingSystem, emailSystem); + systemLandscapeView.AddAnimation(atm); + systemLandscapeView.AddAnimation(customerServiceStaff, backOfficeStaff); + + systemContextView.AddAnimation(internetBankingSystem); + systemContextView.AddAnimation(customer); + systemContextView.AddAnimation(mainframeBankingSystem); + systemContextView.AddAnimation(emailSystem); + + containerView.AddAnimation(customer, mainframeBankingSystem, emailSystem); + containerView.AddAnimation(webApplication); + containerView.AddAnimation(singlePageApplication); + containerView.AddAnimation(mobileApp); + containerView.AddAnimation(apiApplication); + containerView.AddAnimation(database); + + componentView.AddAnimation(singlePageApplication, mobileApp); + componentView.AddAnimation(signinController, securityComponent, database); + componentView.AddAnimation(accountsSummaryController, mainframeBankingSystemFacade, mainframeBankingSystem); + 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, "Validates credentials using", securityComponent); + dynamicView.Add(securityComponent, "select * from users where username = ?", database); + dynamicView.Add(database, "Returns user data to", securityComponent); + dynamicView.Add(securityComponent, "Returns true if the hashed password matches", signinController); + dynamicView.Add(signinController, "Sends back an authentication token to", singlePageApplication); + dynamicView.PaperSize = PaperSize.A5_Landscape; + + DeploymentView developmentDeploymentView = views.CreateDeploymentView(internetBankingSystem, "DevelopmentDeployment", "An example development deployment scenario for the Internet Banking System."); + developmentDeploymentView.Environment = "Development"; + developmentDeploymentView.Add(developerLaptop); + developmentDeploymentView.Add(bigBankDataCenterForDevelopment); + developmentDeploymentView.PaperSize = PaperSize.A5_Landscape; + + developmentDeploymentView.AddAnimation(developmentSinglePageApplication); + developmentDeploymentView.AddAnimation(developmentWebApplication, developmentApiApplication); + developmentDeploymentView.AddAnimation(developmentDatabase); + developmentDeploymentView.AddAnimation(developmentMainframeBankingSystem); + + DeploymentView liveDeploymentView = views.CreateDeploymentView(internetBankingSystem, "LiveDeployment", "An example live deployment scenario for the Internet Banking System."); + liveDeploymentView.Environment = "Live"; + liveDeploymentView.Add(bigBankDataCenterForLive); + liveDeploymentView.Add(customerMobileDevice); + liveDeploymentView.Add(customerComputer); + liveDeploymentView.Add(dataReplicationRelationship); + liveDeploymentView.PaperSize = PaperSize.A5_Landscape; + + liveDeploymentView.AddAnimation(liveSinglePageApplication); + liveDeploymentView.AddAnimation(liveMobileApp); + liveDeploymentView.AddAnimation(liveWebApplication, liveApiApplication); + liveDeploymentView.AddAnimation(livePrimaryDatabase); + liveDeploymentView.AddAnimation(liveSecondaryDatabase); + liveDeploymentView.AddAnimation(liveMainframeBankingSystem); + + // colours, shapes and other diagram styling + Styles styles = views.Configuration.Styles; + styles.Add(new ElementStyle(Tags.SoftwareSystem) { Background = "#1168bd", Color = "#ffffff" }); + styles.Add(new ElementStyle(Tags.Container) { Background = "#438dd5", Color = "#ffffff" }); + styles.Add(new ElementStyle(Tags.Component) { Background = "#85bbf0", Color = "#000000" }); + styles.Add(new ElementStyle(Tags.Person) { Background = "#08427b", Color = "#ffffff", Shape = Shape.Person, FontSize = 22 }); + styles.Add(new ElementStyle(ExistingSystemTag) { Background = "#999999", Color = "#ffffff" }); + styles.Add(new ElementStyle(BankStaffTag) { Background = "#999999", Color = "#ffffff" }); + styles.Add(new ElementStyle(WebBrowserTag) { Shape = Shape.WebBrowser }); + styles.Add(new ElementStyle(MobileAppTag) { Shape = Shape.MobileDeviceLandscape }); + styles.Add(new ElementStyle(DatabaseTag) { Shape = Shape.Cylinder }); + styles.Add(new ElementStyle(FailoverTag) { Opacity = 25 }); + styles.Add(new RelationshipStyle(FailoverTag) {Opacity = 25, Position = 70 }); + + // documentation + // - usually the documentation would be included from separate Markdown/AsciiDoc files, but this is just an example + StructurizrDocumentationTemplate template = new StructurizrDocumentationTemplate(workspace); + template.AddContextSection(internetBankingSystem, Format.Markdown, + "Here is some context about the Internet Banking System...\n" + + "![](embed:SystemLandscape)\n" + + "![](embed:SystemContext)\n" + + "### Internet Banking System\n...\n" + + "### Mainframe Banking System\n...\n"); + template.AddContainersSection(internetBankingSystem, Format.Markdown, + "Here is some information about the containers within the Internet Banking System...\n" + + "![](embed:Containers)\n" + + "### Web Application\n...\n" + + "### Database\n...\n"); + template.AddComponentsSection(webApplication, Format.Markdown, + "Here is some information about the API Application...\n" + + "![](embed:Components)\n" + + "### Sign in process\n" + + "Here is some information about the Sign In Controller, including how the sign in process works...\n" + + "![](embed:SignIn)"); + template.AddDevelopmentEnvironmentSection(internetBankingSystem, Format.AsciiDoc, + "Here is some information about how to set up a development environment for the Internet Banking System...\n" + + "image::embed:DevelopmentDeployment[]"); + template.AddDeploymentSection(internetBankingSystem, Format.AsciiDoc, + "Here is some information about the live deployment environment for the Internet Banking System...\n" + + "image::embed:LiveDeployment[]"); + + return workspace; } - + static void Main() { StructurizrClient structurizrClient = new StructurizrClient(ApiKey, ApiSecret); structurizrClient.PutWorkspace(WorkspaceId, Create()); } + } + } \ No newline at end of file diff --git a/Structurizr.Examples/C4PlantUML.cs b/Structurizr.Examples/C4PlantUML.cs index 44fdfc3..7ea8656 100644 --- a/Structurizr.Examples/C4PlantUML.cs +++ b/Structurizr.Examples/C4PlantUML.cs @@ -42,21 +42,48 @@ static void Main() Console.WriteLine(stringWriter.ToString()); } - + // + // Mark containers or components as database or via tags + // Container webApplication = softwareSystem.AddContainer("Web Application", "Delivers content", "Java and spring MVC"); + // Additional tag element + webApplication.Tags = "Single Page App"; + Container database = softwareSystem.AddContainer("Database", "Stores information", "Relational Database Schema"); // Additional mark it as database database.SetIsDatabase(true); - user.Uses(webApplication, "uses", "HTTP"); + + var httpCall = user.Uses(webApplication, "uses", "HTTP"); + // Additional tag relationship + httpCall.Tags = "via firewall"; + webApplication.Uses(database, "Reads from and writes to", "JDBC").SetDirection(DirectionValues.Right); + // add corresponding styles + var styles = views.Configuration.Styles; + styles.Add(new ElementStyle("Single Page App") {Background = "#5F9061", Stroke = "#2E4F2E", Color = "#FFFFFF", Shape = Shape.RoundedBox }); // rounded box is supported with next version see below + styles.Add(new RelationshipStyle("via firewall") {Color = "#B40404", Dashed = true }); // dashed is supported with next version see below + var containerView = views.CreateContainerView(softwareSystem, "containers", ""); containerView.AddAllElements(); using (var stringWriter = new StringWriter()) { var plantUmlWriter = new C4PlantUmlWriter(); - plantUmlWriter.Write(containerView, stringWriter); + plantUmlWriter.Write(containerView, workspace.Views.Configuration, stringWriter); + Console.WriteLine(stringWriter.ToString()); + } + + // + // Use features of the next planned C4-PlantUML version (v2.3.0 ?) + // + using (var stringWriter = new StringWriter()) + { + var plantUmlWriter = new C4PlantUmlWriter(); + plantUmlWriter.EnableNextFeatures = true; + plantUmlWriter.CustomBaseUrl = "https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/"; + + plantUmlWriter.Write(containerView, workspace.Views.Configuration, stringWriter); Console.WriteLine(stringWriter.ToString()); } } diff --git a/Structurizr.Examples/Structurizr.Examples.csproj b/Structurizr.Examples/Structurizr.Examples.csproj index 6de67c5..29b95ca 100644 --- a/Structurizr.Examples/Structurizr.Examples.csproj +++ b/Structurizr.Examples/Structurizr.Examples.csproj @@ -1,12 +1,12 @@  Exe - netcoreapp1.1 + netcoreapp2.1 Structurizr.Examples.PlantUML false - + diff --git a/Structurizr.PlantUML.Tests/IO/C4PlantUML/C4PlantUmlWriterTests.cs b/Structurizr.PlantUML.Tests/IO/C4PlantUML/C4PlantUmlWriterTests.cs index 36fc4f1..e595fc4 100644 --- a/Structurizr.PlantUML.Tests/IO/C4PlantUML/C4PlantUmlWriterTests.cs +++ b/Structurizr.PlantUML.Tests/IO/C4PlantUML/C4PlantUmlWriterTests.cs @@ -29,14 +29,14 @@ public void test_writeBigBankPlcWorkspace() _workspace = BigBankPlc.Create(); AddLayoutDetails(_workspace); -/* - using (var writer = new StringWriter()) - { - new Structurizr.IO.Json.JsonWriter(true).Write(_workspace, writer); - var json = writer.GetStringBuilder().ToString(); - json = json; - } -*/ + /* + using (var writer = new StringWriter()) + { + new Structurizr.IO.Json.JsonWriter(true).Write(_workspace, writer); + var json = writer.GetStringBuilder().ToString(); + json = json; + } + */ _plantUMLWriter.Write(_workspace, _stringWriter); Assert.Equal( @@ -46,26 +46,33 @@ public void test_writeBigBankPlcWorkspace() ' Structurizr.SystemLandscapeView: SystemLandscape title System Landscape for Big Bank plc -LAYOUT_WITH_LEGEND() +UpdateElementStyle(system, $bgColor = ""#1168bd"", $fontColor = ""#ffffff"", $borderColor = ""#1168bd"") +UpdateElementStyle(container, $bgColor = ""#438dd5"", $fontColor = ""#ffffff"", $borderColor = ""#438dd5"") +UpdateElementStyle(component, $bgColor = ""#85bbf0"", $fontColor = ""#000000"", $borderColor = ""#85bbf0"") +UpdateElementStyle(person, $bgColor = ""#08427b"", $fontColor = ""#ffffff"", $borderColor = ""#08427b"") +AddElementTag(Existing System, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") +AddElementTag(Bank Staff, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") -Person_Ext(PersonalBankingCustomer__9bc576, ""Personal Banking Customer"", ""A customer of the bank, with personal bank accounts."") +Person_Ext(PersonalBankingCustomer__HASH0, ""Personal Banking Customer"", ""A customer of the bank, with personal bank accounts."") Enterprise_Boundary(BigBankplc, ""Big Bank plc"") { - Person(BackOfficeStaff__5f761d, ""Back Office Staff"", ""Administration and support staff within the bank."") - Person(CustomerServiceStaff__a35be5, ""Customer Service Staff"", ""Customer service staff within the bank."") - System(ATM__22fc739, ""ATM"", ""Allows customers to withdraw cash."") - System(EmailSystem__2908eb9, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."") - System(InternetBankingSystem__2aef74c, ""Internet Banking System"", ""Allows customers to view information about their bank accounts, and make payments."") - System(MainframeBankingSystem__f50ffa, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."") + Person(BackOfficeStaff__HASH1, ""Back Office Staff"", ""Administration and support staff within the bank."", $tags=""Bank Staff"") + Person(CustomerServiceStaff__HASH2, ""Customer Service Staff"", ""Customer service staff within the bank."", $tags=""Bank Staff"") + System(ATM__HASH3, ""ATM"", ""Allows customers to withdraw cash."", $tags=""Existing System"") + System(EmailSystem__HASH4, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."", $tags=""Existing System"") + System(InternetBankingSystem__HASH5, ""Internet Banking System"", ""Allows customers to view information about their bank accounts, and make payments."") + System(MainframeBankingSystem__HASH6, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."", $tags=""Existing System"") } -Rel_Down(ATM__22fc739, MainframeBankingSystem__f50ffa, ""Uses"") -Rel_Down(BackOfficeStaff__5f761d, MainframeBankingSystem__f50ffa, ""Uses"") -Rel_Down(CustomerServiceStaff__a35be5, MainframeBankingSystem__f50ffa, ""Uses"") -Rel_Up(EmailSystem__2908eb9, PersonalBankingCustomer__9bc576, ""Sends e-mails to"") -Rel_Down(InternetBankingSystem__2aef74c, EmailSystem__2908eb9, ""Sends e-mail using"") -Rel_Down(InternetBankingSystem__2aef74c, MainframeBankingSystem__f50ffa, ""Uses"") -Rel(PersonalBankingCustomer__9bc576, ATM__22fc739, ""Withdraws cash using"") -Rel(PersonalBankingCustomer__9bc576, CustomerServiceStaff__a35be5, ""Asks questions to"", ""Telephone"") -Rel(PersonalBankingCustomer__9bc576, InternetBankingSystem__2aef74c, ""Uses"") +Rel_Down(ATM__HASH3, MainframeBankingSystem__HASH6, ""Uses"") +Rel_Down(BackOfficeStaff__HASH1, MainframeBankingSystem__HASH6, ""Uses"") +Rel_Down(CustomerServiceStaff__HASH2, MainframeBankingSystem__HASH6, ""Uses"") +Rel_Up(EmailSystem__HASH4, PersonalBankingCustomer__HASH0, ""Sends e-mails to"") +Rel_Down(InternetBankingSystem__HASH5, EmailSystem__HASH4, ""Sends e-mail using"") +Rel_Down(InternetBankingSystem__HASH5, MainframeBankingSystem__HASH6, ""Gets account information from, and makes payments using"") +Rel(PersonalBankingCustomer__HASH0, ATM__HASH3, ""Withdraws cash using"") +Rel(PersonalBankingCustomer__HASH0, CustomerServiceStaff__HASH2, ""Asks questions to"", ""Telephone"") +Rel(PersonalBankingCustomer__HASH0, InternetBankingSystem__HASH5, ""Views account balances, and makes payments using"") + +SHOW_LEGEND() @enduml @startuml @@ -74,16 +81,23 @@ public void test_writeBigBankPlcWorkspace() ' Structurizr.SystemContextView: SystemContext title Internet Banking System - System Context -LAYOUT_WITH_LEGEND() - -System(EmailSystem__2908eb9, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."") -System(InternetBankingSystem__2aef74c, ""Internet Banking System"", ""Allows customers to view information about their bank accounts, and make payments."") -System(MainframeBankingSystem__f50ffa, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."") -Person_Ext(PersonalBankingCustomer__9bc576, ""Personal Banking Customer"", ""A customer of the bank, with personal bank accounts."") -Rel_Up(EmailSystem__2908eb9, PersonalBankingCustomer__9bc576, ""Sends e-mails to"") -Rel_Right(InternetBankingSystem__2aef74c, EmailSystem__2908eb9, ""Sends e-mail using"") -Rel(InternetBankingSystem__2aef74c, MainframeBankingSystem__f50ffa, ""Uses"") -Rel(PersonalBankingCustomer__9bc576, InternetBankingSystem__2aef74c, ""Uses"") +UpdateElementStyle(system, $bgColor = ""#1168bd"", $fontColor = ""#ffffff"", $borderColor = ""#1168bd"") +UpdateElementStyle(container, $bgColor = ""#438dd5"", $fontColor = ""#ffffff"", $borderColor = ""#438dd5"") +UpdateElementStyle(component, $bgColor = ""#85bbf0"", $fontColor = ""#000000"", $borderColor = ""#85bbf0"") +UpdateElementStyle(person, $bgColor = ""#08427b"", $fontColor = ""#ffffff"", $borderColor = ""#08427b"") +AddElementTag(Existing System, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") +AddElementTag(Bank Staff, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") + +System(EmailSystem__HASH4, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."", $tags=""Existing System"") +System(InternetBankingSystem__HASH5, ""Internet Banking System"", ""Allows customers to view information about their bank accounts, and make payments."") +System(MainframeBankingSystem__HASH6, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."", $tags=""Existing System"") +Person_Ext(PersonalBankingCustomer__HASH0, ""Personal Banking Customer"", ""A customer of the bank, with personal bank accounts."") +Rel_Up(EmailSystem__HASH4, PersonalBankingCustomer__HASH0, ""Sends e-mails to"") +Rel_Right(InternetBankingSystem__HASH5, EmailSystem__HASH4, ""Sends e-mail using"") +Rel(InternetBankingSystem__HASH5, MainframeBankingSystem__HASH6, ""Gets account information from, and makes payments using"") +Rel(PersonalBankingCustomer__HASH0, InternetBankingSystem__HASH5, ""Views account balances, and makes payments using"") + +SHOW_LEGEND() @enduml @startuml @@ -92,28 +106,35 @@ title Internet Banking System - System Context ' Structurizr.ContainerView: Containers title Internet Banking System - Containers -LAYOUT_WITH_LEGEND() - -System(EmailSystem__2908eb9, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."") -System(MainframeBankingSystem__f50ffa, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."") -Person_Ext(PersonalBankingCustomer__9bc576, ""Personal Banking Customer"", ""A customer of the bank, with personal bank accounts."") -System_Boundary(InternetBankingSystem__2aef74c, ""Internet Banking System"") { - Container(InternetBankingSystem__APIApplication__2c36bed, ""API Application"", ""Java and Spring MVC"", ""Provides Internet banking functionality via a JSON/HTTPS API."") - ContainerDb(InternetBankingSystem__Database__18307f7, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."") - Container(InternetBankingSystem__MobileApp__38a070b, ""Mobile App"", ""Xamarin"", ""Provides a limited subset of the Internet banking functionality to customers via their mobile device."") - Container(InternetBankingSystem__SinglePageApplication__1414c79, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."") - Container(InternetBankingSystem__WebApplication__1bb919c, ""Web Application"", ""Java and Spring MVC"", ""Delivers the static content and the Internet banking single page application."") +UpdateElementStyle(system, $bgColor = ""#1168bd"", $fontColor = ""#ffffff"", $borderColor = ""#1168bd"") +UpdateElementStyle(container, $bgColor = ""#438dd5"", $fontColor = ""#ffffff"", $borderColor = ""#438dd5"") +UpdateElementStyle(component, $bgColor = ""#85bbf0"", $fontColor = ""#000000"", $borderColor = ""#85bbf0"") +UpdateElementStyle(person, $bgColor = ""#08427b"", $fontColor = ""#ffffff"", $borderColor = ""#08427b"") +AddElementTag(Existing System, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") +AddElementTag(Bank Staff, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") + +System(EmailSystem__HASH4, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."", $tags=""Existing System"") +System(MainframeBankingSystem__HASH6, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."", $tags=""Existing System"") +Person_Ext(PersonalBankingCustomer__HASH0, ""Personal Banking Customer"", ""A customer of the bank, with personal bank accounts."") +System_Boundary(InternetBankingSystem__HASH5, ""Internet Banking System"") { + Container(InternetBankingSystem__APIApplication__HASH7, ""API Application"", ""Java and Spring MVC"", ""Provides Internet banking functionality via a JSON/HTTPS API."") + ContainerDb(InternetBankingSystem__Database__HASH8, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."", $tags=""Database"") + Container(InternetBankingSystem__MobileApp__HASH9, ""Mobile App"", ""Xamarin"", ""Provides a limited subset of the Internet banking functionality to customers via their mobile device."", $tags=""Mobile App"") + Container(InternetBankingSystem__SinglePageApplication__HASH10, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."", $tags=""Web Browser"") + Container(InternetBankingSystem__WebApplication__HASH11, ""Web Application"", ""Java and Spring MVC"", ""Delivers the static content and the Internet banking single page application."") } -Rel_Left(InternetBankingSystem__APIApplication__2c36bed, InternetBankingSystem__Database__18307f7, ""Reads from and writes to"", ""JDBC"") -Rel_Up(InternetBankingSystem__APIApplication__2c36bed, EmailSystem__2908eb9, ""Sends e-mail using"", ""SMTP"") -Rel_Left(InternetBankingSystem__APIApplication__2c36bed, MainframeBankingSystem__f50ffa, ""Uses"", ""XML/HTTPS"") -Rel_Up(EmailSystem__2908eb9, PersonalBankingCustomer__9bc576, ""Sends e-mails to"") -Rel(InternetBankingSystem__MobileApp__38a070b, InternetBankingSystem__APIApplication__2c36bed, ""Makes API calls to"", ""JSON/HTTPS"") -Rel(PersonalBankingCustomer__9bc576, InternetBankingSystem__MobileApp__38a070b, ""Uses"") -Rel(PersonalBankingCustomer__9bc576, InternetBankingSystem__SinglePageApplication__1414c79, ""Uses"") -Rel(PersonalBankingCustomer__9bc576, InternetBankingSystem__WebApplication__1bb919c, ""Uses"", ""HTTPS"") -Rel(InternetBankingSystem__SinglePageApplication__1414c79, InternetBankingSystem__APIApplication__2c36bed, ""Makes API calls to"", ""JSON/HTTPS"") -Rel_Right(InternetBankingSystem__WebApplication__1bb919c, InternetBankingSystem__SinglePageApplication__1414c79, ""Delivers to the customer's web browser"") +Rel_Left(InternetBankingSystem__APIApplication__HASH7, InternetBankingSystem__Database__HASH8, ""Reads from and writes to"", ""JDBC"") +Rel_Up(InternetBankingSystem__APIApplication__HASH7, EmailSystem__HASH4, ""Sends e-mail using"", ""SMTP"") +Rel_Left(InternetBankingSystem__APIApplication__HASH7, MainframeBankingSystem__HASH6, ""Makes API calls to"", ""XML/HTTPS"") +Rel_Up(EmailSystem__HASH4, PersonalBankingCustomer__HASH0, ""Sends e-mails to"") +Rel(InternetBankingSystem__MobileApp__HASH9, InternetBankingSystem__APIApplication__HASH7, ""Makes API calls to"", ""JSON/HTTPS"") +Rel(PersonalBankingCustomer__HASH0, InternetBankingSystem__MobileApp__HASH9, ""Views account balances, and makes payments using"") +Rel(PersonalBankingCustomer__HASH0, InternetBankingSystem__SinglePageApplication__HASH10, ""Views account balances, and makes payments using"") +Rel(PersonalBankingCustomer__HASH0, InternetBankingSystem__WebApplication__HASH11, ""Visits bigbank.com/ib using"", ""HTTPS"") +Rel(InternetBankingSystem__SinglePageApplication__HASH10, InternetBankingSystem__APIApplication__HASH7, ""Makes API calls to"", ""JSON/HTTPS"") +Rel_Right(InternetBankingSystem__WebApplication__HASH11, InternetBankingSystem__SinglePageApplication__HASH10, ""Delivers to the customer's web browser"") + +SHOW_LEGEND() @enduml @startuml @@ -122,374 +143,169 @@ title Internet Banking System - Containers ' Structurizr.ComponentView: Components title Internet Banking System - API Application - Components -LAYOUT_WITH_LEGEND() - -ContainerDb(InternetBankingSystem__Database__18307f7, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."") -System(EmailSystem__2908eb9, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."") -System(MainframeBankingSystem__f50ffa, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."") -Container(InternetBankingSystem__MobileApp__38a070b, ""Mobile App"", ""Xamarin"", ""Provides a limited subset of the Internet banking functionality to customers via their mobile device."") -Container(InternetBankingSystem__SinglePageApplication__1414c79, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."") -Container_Boundary(InternetBankingSystem__APIApplication__2c36bed, ""API Application"") { - Component(InternetBankingSystem__APIApplication__AccountsSummaryController__3f81fb2, ""Accounts Summary Controller"", ""Spring MVC Rest Controller"", ""Provides customers with a summary of their bank accounts."") - Component(InternetBankingSystem__APIApplication__EmailComponent__24ec565, ""E-mail Component"", ""Spring Bean"", ""Sends e-mails to users."") - Component(InternetBankingSystem__APIApplication__MainframeBankingSystemFacade__2493dd9, ""Mainframe Banking System Facade"", ""Spring Bean"", ""A facade onto the mainframe banking system."") - Component(InternetBankingSystem__APIApplication__ResetPasswordController__23f0eac, ""Reset Password Controller"", ""Spring MVC Rest Controller"", ""Allows users to reset their passwords with a single use URL."") - Component(InternetBankingSystem__APIApplication__SecurityComponent__a4474, ""Security Component"", ""Spring Bean"", ""Provides functionality related to signing in, changing passwords, etc."") - Component(InternetBankingSystem__APIApplication__SignInController__22cc62b, ""Sign In Controller"", ""Spring MVC Rest Controller"", ""Allows users to sign in to the Internet Banking System."") +UpdateElementStyle(system, $bgColor = ""#1168bd"", $fontColor = ""#ffffff"", $borderColor = ""#1168bd"") +UpdateElementStyle(container, $bgColor = ""#438dd5"", $fontColor = ""#ffffff"", $borderColor = ""#438dd5"") +UpdateElementStyle(component, $bgColor = ""#85bbf0"", $fontColor = ""#000000"", $borderColor = ""#85bbf0"") +UpdateElementStyle(person, $bgColor = ""#08427b"", $fontColor = ""#ffffff"", $borderColor = ""#08427b"") +AddElementTag(Existing System, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") +AddElementTag(Bank Staff, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") + +ContainerDb(InternetBankingSystem__Database__HASH8, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."", $tags=""Database"") +System(EmailSystem__HASH4, ""E-mail System"", ""The internal Microsoft Exchange e-mail system."", $tags=""Existing System"") +System(MainframeBankingSystem__HASH6, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."", $tags=""Existing System"") +Container(InternetBankingSystem__MobileApp__HASH9, ""Mobile App"", ""Xamarin"", ""Provides a limited subset of the Internet banking functionality to customers via their mobile device."", $tags=""Mobile App"") +Container(InternetBankingSystem__SinglePageApplication__HASH10, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."", $tags=""Web Browser"") +Container_Boundary(InternetBankingSystem__APIApplication__HASH7, ""API Application"") { + Component(InternetBankingSystem__APIApplication__AccountsSummaryController__HASH12, ""Accounts Summary Controller"", ""Spring MVC Rest Controller"", ""Provides customers with a summary of their bank accounts."") + Component(InternetBankingSystem__APIApplication__EmailComponent__HASH13, ""E-mail Component"", ""Spring Bean"", ""Sends e-mails to users."") + Component(InternetBankingSystem__APIApplication__MainframeBankingSystemFacade__HASH14, ""Mainframe Banking System Facade"", ""Spring Bean"", ""A facade onto the mainframe banking system."") + Component(InternetBankingSystem__APIApplication__ResetPasswordController__HASH15, ""Reset Password Controller"", ""Spring MVC Rest Controller"", ""Allows users to reset their passwords with a single use URL."") + Component(InternetBankingSystem__APIApplication__SecurityComponent__HASH16, ""Security Component"", ""Spring Bean"", ""Provides functionality related to signing in, changing passwords, etc."") + Component(InternetBankingSystem__APIApplication__SignInController__HASH17, ""Sign In Controller"", ""Spring MVC Rest Controller"", ""Allows users to sign in to the Internet Banking System."") } -Rel(InternetBankingSystem__APIApplication__AccountsSummaryController__3f81fb2, InternetBankingSystem__APIApplication__MainframeBankingSystemFacade__2493dd9, ""Uses"") -Rel(InternetBankingSystem__APIApplication__EmailComponent__24ec565, EmailSystem__2908eb9, ""Sends e-mail using"") -Rel(InternetBankingSystem__APIApplication__MainframeBankingSystemFacade__2493dd9, MainframeBankingSystem__f50ffa, ""Uses"", ""XML/HTTPS"") -Rel(InternetBankingSystem__MobileApp__38a070b, InternetBankingSystem__APIApplication__AccountsSummaryController__3f81fb2, ""Makes API calls to"", ""JSON/HTTPS"") -Rel(InternetBankingSystem__MobileApp__38a070b, InternetBankingSystem__APIApplication__ResetPasswordController__23f0eac, ""Makes API calls to"", ""JSON/HTTPS"") -Rel(InternetBankingSystem__MobileApp__38a070b, InternetBankingSystem__APIApplication__SignInController__22cc62b, ""Makes API calls to"", ""JSON/HTTPS"") -Rel(InternetBankingSystem__APIApplication__ResetPasswordController__23f0eac, InternetBankingSystem__APIApplication__EmailComponent__24ec565, ""Uses"") -Rel(InternetBankingSystem__APIApplication__ResetPasswordController__23f0eac, InternetBankingSystem__APIApplication__SecurityComponent__a4474, ""Uses"") -Rel(InternetBankingSystem__APIApplication__SecurityComponent__a4474, InternetBankingSystem__Database__18307f7, ""Reads from and writes to"", ""JDBC"") -Rel(InternetBankingSystem__APIApplication__SignInController__22cc62b, InternetBankingSystem__APIApplication__SecurityComponent__a4474, ""Uses"") -Rel(InternetBankingSystem__SinglePageApplication__1414c79, InternetBankingSystem__APIApplication__AccountsSummaryController__3f81fb2, ""Makes API calls to"", ""JSON/HTTPS"") -Rel(InternetBankingSystem__SinglePageApplication__1414c79, InternetBankingSystem__APIApplication__ResetPasswordController__23f0eac, ""Makes API calls to"", ""JSON/HTTPS"") -Rel(InternetBankingSystem__SinglePageApplication__1414c79, InternetBankingSystem__APIApplication__SignInController__22cc62b, ""Makes API calls to"", ""JSON/HTTPS"") +Rel(InternetBankingSystem__APIApplication__AccountsSummaryController__HASH12, InternetBankingSystem__APIApplication__MainframeBankingSystemFacade__HASH14, ""Uses"") +Rel(InternetBankingSystem__APIApplication__EmailComponent__HASH13, EmailSystem__HASH4, ""Sends e-mail using"") +Rel(InternetBankingSystem__APIApplication__MainframeBankingSystemFacade__HASH14, MainframeBankingSystem__HASH6, ""Uses"", ""XML/HTTPS"") +Rel(InternetBankingSystem__MobileApp__HASH9, InternetBankingSystem__APIApplication__AccountsSummaryController__HASH12, ""Makes API calls to"", ""JSON/HTTPS"") +Rel(InternetBankingSystem__MobileApp__HASH9, InternetBankingSystem__APIApplication__ResetPasswordController__HASH15, ""Makes API calls to"", ""JSON/HTTPS"") +Rel(InternetBankingSystem__MobileApp__HASH9, InternetBankingSystem__APIApplication__SignInController__HASH17, ""Makes API calls to"", ""JSON/HTTPS"") +Rel(InternetBankingSystem__APIApplication__ResetPasswordController__HASH15, InternetBankingSystem__APIApplication__EmailComponent__HASH13, ""Uses"") +Rel(InternetBankingSystem__APIApplication__ResetPasswordController__HASH15, InternetBankingSystem__APIApplication__SecurityComponent__HASH16, ""Uses"") +Rel(InternetBankingSystem__APIApplication__SecurityComponent__HASH16, InternetBankingSystem__Database__HASH8, ""Reads from and writes to"", ""JDBC"") +Rel(InternetBankingSystem__APIApplication__SignInController__HASH17, InternetBankingSystem__APIApplication__SecurityComponent__HASH16, ""Uses"") +Rel(InternetBankingSystem__SinglePageApplication__HASH10, InternetBankingSystem__APIApplication__AccountsSummaryController__HASH12, ""Makes API calls to"", ""JSON/HTTPS"") +Rel(InternetBankingSystem__SinglePageApplication__HASH10, InternetBankingSystem__APIApplication__ResetPasswordController__HASH15, ""Makes API calls to"", ""JSON/HTTPS"") +Rel(InternetBankingSystem__SinglePageApplication__HASH10, InternetBankingSystem__APIApplication__SignInController__HASH17, ""Makes API calls to"", ""JSON/HTTPS"") + +SHOW_LEGEND() @enduml @startuml -!include -' C4_Dynamic.puml is missing, simulate it with following definitions -' Scope: Interactions in an enterprise, software system or container. -' Primary and supporting elements: Depends on the diagram scope - -' enterprise - people and software systems related to the enterprise in scope -' software system - see system context or container diagrams, -' container - see component diagram. -' Intended audience: Technical and non-technical people, inside and outside of the software development team. - -' Dynamic diagram introduces (automatically) numbered interactions: -' Interact(): used automatic calculated index, -' Interact2(): index can be explicit defined, -' SetIndex(): set the next index, -' GetIndex(): get the index and automatically increase index - -' Index -' ################################## - -!function $inc_($value, $step=1) - !return $value + $step -!endfunction - -!$index=1 - -!function SetIndex($new_index) - !$index=$new_index -!endfunction - -!function GetIndex($auto_increase=1) - !$old = $index - !$index=$inc_($index, $auto_increase) - !return $old -!endfunction - -' Interact -' ################################## -!define Interact2(e_index, e_from, e_to, e_label) Rel(e_from, e_to, ""e_index: e_label"") -!define Interact2(e_index, e_from, e_to, e_label, e_techn) Rel(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_Back(e_index, e_from, e_to, e_label) Rel_Back(e_from, e_to, ""e_index: e_label"") -!define Interact2_Back(e_index, e_from, e_to, e_label, e_techn) Rel_Back(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_Neighbor(e_index, e_from, e_to, e_label) Rel_Neighbor(e_from, e_to, ""e_index: e_label"") -!define Interact2_Neighbor(e_index, e_from, e_to, e_label, e_techn) Rel_Neighbor(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_Back_Neighbor(e_index, e_from, e_to, e_label) Rel_Back_Neighbor(e_from, e_to, ""e_index: e_label"") -!define Interact2_Back_Neighbor(e_index, e_from, e_to, e_label, e_techn) Rel_Back_Neighbor(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_D(e_index, e_from, e_to, e_label) Rel_D(e_from, e_to, ""e_index: e_label"") -!define Interact2_D(e_index, e_from, e_to, e_label, e_techn) Rel_D(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Down(e_index, e_from, e_to, e_label) Rel_Down(e_from, e_to, ""e_index: e_label"") -!define Interact2_Down(e_index, e_from, e_to, e_label, e_techn) Rel_Down(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_U(e_index, e_from, e_to, e_label) Rel_U(e_from, e_to, ""e_index: e_label"") -!define Interact2_U(e_index, e_from, e_to, e_label, e_techn) Rel_U(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Up(e_index, e_from, e_to, e_label) Rel_Up(e_from, e_to, ""e_index: e_label"") -!define Interact2_Up(e_index, e_from, e_to, e_label, e_techn) Rel_Up(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_L(e_index, e_from, e_to, e_label) Rel_L(e_from, e_to, ""e_index: e_label"") -!define Interact2_L(e_index, e_from, e_to, e_label, e_techn) Rel_L(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Left(e_index, e_from, e_to, e_label) Rel_Left(e_from, e_to, ""e_index: e_label"") -!define Interact2_Left(e_index, e_from, e_to, e_label, e_techn) Rel_Left(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_R(e_index, e_from, e_to, e_label) Rel_R(e_from, e_to, ""e_index: e_label"") -!define Interact2_R(e_index, e_from, e_to, e_label, e_techn) Rel_R(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Right(e_index, e_from, e_to, e_label) Rel_Right(e_from, e_to, ""e_index: e_label"") -!define Interact2_Right(e_index, e_from, e_to, e_label, e_techn) Rel_Right(e_from, e_to, ""e_index: e_label"", e_techn) - -!unquoted function Interact($e_from, $e_to, $e_label) - Interact2($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact($e_from, $e_to, $e_label, $e_techn) - Interact2($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_Back($e_from, $e_to, $e_label) - Interact2_Back($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Back($e_from, $e_to, $e_label, $e_techn) - Interact2_Back($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_Neighbor($e_from, $e_to, $e_label) - Interact2_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Neighbor($e_from, $e_to, $e_label, $e_techn) - Interact2_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_Back_Neighbor($e_from, $e_to, $e_label) - Interact2_Back_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Back_Neighbor($e_from, $e_to, $e_label, $e_techn) - Interact2_Back_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_D($e_from, $e_to, $e_label) - Interact2_D($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_D($e_from, $e_to, $e_label, $e_techn) - Interact2_D($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Down($e_from, $e_to, $e_label) - Interact2_Down($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Down($e_from, $e_to, $e_label, $e_techn) - Interact2_Down($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_U($e_from, $e_to, $e_label) - Interact2_U($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_U($e_from, $e_to, $e_label, $e_techn) - Interact2_U($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Up($e_from, $e_to, $e_label) - Interact2_Up($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Up($e_from, $e_to, $e_label, $e_techn) - Interact2_Up($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_L($e_from, $e_to, $e_label) - Interact2_L($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_L($e_from, $e_to, $e_label, $e_techn) - Interact2_L($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Left($e_from, $e_to, $e_label) - Interact2_Left($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Left($e_from, $e_to, $e_label, $e_techn) - Interact2_Left($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_R($e_from, $e_to, $e_label) - Interact2_R($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_R($e_from, $e_to, $e_label, $e_techn) - Interact2_R($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Right($e_from, $e_to, $e_label) - Interact2_Right($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Right($e_from, $e_to, $e_label, $e_techn) - Interact2_Right($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction +!include ' Structurizr.DynamicView: SignIn title API Application - Dynamic -LAYOUT_WITH_LEGEND() - -ContainerDb(InternetBankingSystem__Database__18307f7, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."") -Container(InternetBankingSystem__SinglePageApplication__1414c79, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."") -Container_Boundary(InternetBankingSystem__APIApplication__2c36bed, ""API Application"") { - Component(InternetBankingSystem__APIApplication__SecurityComponent__a4474, ""Security Component"", ""Spring Bean"", ""Provides functionality related to signing in, changing passwords, etc."") - Component(InternetBankingSystem__APIApplication__SignInController__22cc62b, ""Sign In Controller"", ""Spring MVC Rest Controller"", ""Allows users to sign in to the Internet Banking System."") +UpdateElementStyle(system, $bgColor = ""#1168bd"", $fontColor = ""#ffffff"", $borderColor = ""#1168bd"") +UpdateElementStyle(container, $bgColor = ""#438dd5"", $fontColor = ""#ffffff"", $borderColor = ""#438dd5"") +UpdateElementStyle(component, $bgColor = ""#85bbf0"", $fontColor = ""#000000"", $borderColor = ""#85bbf0"") +UpdateElementStyle(person, $bgColor = ""#08427b"", $fontColor = ""#ffffff"", $borderColor = ""#08427b"") +AddElementTag(Existing System, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") +AddElementTag(Bank Staff, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") + +ContainerDb(InternetBankingSystem__Database__HASH8, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."", $tags=""Database"") +Container(InternetBankingSystem__SinglePageApplication__HASH10, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."", $tags=""Web Browser"") +Container_Boundary(InternetBankingSystem__APIApplication__HASH7, ""API Application"") { + Component(InternetBankingSystem__APIApplication__SecurityComponent__HASH16, ""Security Component"", ""Spring Bean"", ""Provides functionality related to signing in, changing passwords, etc."") + Component(InternetBankingSystem__APIApplication__SignInController__HASH17, ""Sign In Controller"", ""Spring MVC Rest Controller"", ""Allows users to sign in to the Internet Banking System."") } -Interact2_Right(""1"", InternetBankingSystem__SinglePageApplication__1414c79, InternetBankingSystem__APIApplication__SignInController__22cc62b, ""Submits credentials to"", ""JSON/HTTPS"") -Interact2(""2"", InternetBankingSystem__APIApplication__SignInController__22cc62b, InternetBankingSystem__APIApplication__SecurityComponent__a4474, ""Calls isAuthenticated() on"") -Interact2_Right(""3"", InternetBankingSystem__APIApplication__SecurityComponent__a4474, InternetBankingSystem__Database__18307f7, ""select * from users where username = ?"", ""JDBC"") +RelIndex_Right(""1"", InternetBankingSystem__SinglePageApplication__HASH10, InternetBankingSystem__APIApplication__SignInController__HASH17, ""Submits credentials to"", ""JSON/HTTPS"") +RelIndex(""2"", InternetBankingSystem__APIApplication__SignInController__HASH17, InternetBankingSystem__APIApplication__SecurityComponent__HASH16, ""Validates credentials using"") +RelIndex_Right(""3"", InternetBankingSystem__APIApplication__SecurityComponent__HASH16, InternetBankingSystem__Database__HASH8, ""select * from users where username = ?"", ""JDBC"") +RelIndex_Left(""4"", InternetBankingSystem__Database__HASH8, InternetBankingSystem__APIApplication__SecurityComponent__HASH16, ""Returns user data to"", ""JDBC"", $tags=""Back"") +RelIndex(""5"", InternetBankingSystem__APIApplication__SecurityComponent__HASH16, InternetBankingSystem__APIApplication__SignInController__HASH17, ""Returns true if the hashed password matches"", $tags=""Back"") +RelIndex_Left(""6"", InternetBankingSystem__APIApplication__SignInController__HASH17, InternetBankingSystem__SinglePageApplication__HASH10, ""Sends back an authentication token to"", ""JSON/HTTPS"", $tags=""Back"") + +SHOW_LEGEND() @enduml @startuml -!include -' C4_Deployment.puml is missing, simulate it with following definitions -' Scope: A single software system. -' Primary elements: Deployment nodes and containers within the software system in scope. -' Intended audience: Technical people inside and outside of the software development team; including software architects, developers and operations/support staff. - -' Colors -' ################################## -!define NODE_FONT_COLOR #444444 -!define NODE_BG_COLOR #FFFFFF - -' Styling -' ################################## - -skinparam rectangle<> { - Shadowing false - StereotypeFontSize 0 - FontColor NODE_FONT_COLOR - BackgroundColor NODE_BG_COLOR - BorderColor #444444 -} - -' Layout -' ################################## - -!definelong LAYOUT_WITH_LEGEND -hide stereotype -legend right -|= |= Type | -| | deployment node | -| | deployment container | -endlegend -!enddefinelong - -' Nodes -' ################################## -' PlantUML does not support automatic line breaks of container, if e_techn is very long insert line breaks with -' ""\n"" -!define Node(e_alias, e_label, e_techn) rectangle ""==e_label\n[e_techn]"" <> as e_alias +!include ' Structurizr.DeploymentView: DevelopmentDeployment -title Internet Banking System - Deployment - -LAYOUT_WITH_LEGEND() - -Node(Deployment__Development__DeveloperLaptop__389f399, ""Developer Laptop"", ""Microsoft Windows 10 or Apple \nmacOS"") { - Node(Deployment__Development__DeveloperLaptop__DockerContainerWebServer__1b73d2e, ""Docker Container - Web Server"", ""Docker"") { - Node(Deployment__Development__DeveloperLaptop__DockerContainerWebServer__ApacheTomcat__1cc9f55, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { - Container(InternetBankingSystem__WebApplication1__28f79f6, ""Web Application"", ""Java and Spring MVC"", ""Delivers the static content and the Internet banking single page application."") - Container(InternetBankingSystem__APIApplication1__1f227f4, ""API Application"", ""Java and Spring MVC"", ""Provides Internet banking functionality via a JSON/HTTPS API."") +title Internet Banking System - Deployment - Development + +UpdateElementStyle(system, $bgColor = ""#1168bd"", $fontColor = ""#ffffff"", $borderColor = ""#1168bd"") +UpdateElementStyle(container, $bgColor = ""#438dd5"", $fontColor = ""#ffffff"", $borderColor = ""#438dd5"") +UpdateElementStyle(component, $bgColor = ""#85bbf0"", $fontColor = ""#000000"", $borderColor = ""#85bbf0"") +UpdateElementStyle(person, $bgColor = ""#08427b"", $fontColor = ""#ffffff"", $borderColor = ""#08427b"") +AddElementTag(Existing System, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") +AddElementTag(Bank Staff, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") + +Node(BigBankplc__HASH18, ""Big Bank plc"", ""Big Bank plc data center"") { + Node(bigbankdev001__HASH19, ""bigbank-dev001"") { + System(MainframeBankingSystem1__HASH20, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."", $tags=""Software System Instance"") + } +} +Node(DeveloperLaptop__HASH21, ""Developer Laptop"", ""Microsoft Windows 10 or Apple macOS"") { + Node(DockerContainerWebServer__HASH22, ""Docker Container - Web Server"", ""Docker"") { + Node(ApacheTomcat__HASH23, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { + Container(InternetBankingSystem__WebApplication1__HASH24, ""Web Application"", ""Java and Spring MVC"", ""Delivers the static content and the Internet banking single page application."", $tags=""Container Instance"") + Container(InternetBankingSystem__APIApplication1__HASH25, ""API Application"", ""Java and Spring MVC"", ""Provides Internet banking functionality via a JSON/HTTPS API."", $tags=""Container Instance"") } } - Node(Deployment__Development__DeveloperLaptop__DockerContainerDatabaseServer__2eae566, ""Docker Container - Database Server"", ""Docker"") { - Node(Deployment__Development__DeveloperLaptop__DockerContainerDatabaseServer__DatabaseServer__24d13de, ""Database Server"", ""Oracle 12c"") { - ContainerDb(InternetBankingSystem__Database1__3296ca6, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."") + Node(DockerContainerDatabaseServer__HASH26, ""Docker Container - Database Server"", ""Docker"") { + Node(DatabaseServer__HASH27, ""Database Server"", ""Oracle 12c"") { + ContainerDb(InternetBankingSystem__Database1__HASH28, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."", $tags=""Container Instance"") } } - Node(Deployment__Development__DeveloperLaptop__WebBrowser__3930fd, ""Web Browser"", ""Google Chrome, Mozilla \nFirefox, Apple Safari or \nMicrosoft Edge"") { - Container(InternetBankingSystem__SinglePageApplication1__bbe85d, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."") + Node(WebBrowser__HASH29, ""Web Browser"", ""Chrome, Firefox, Safari, or Edge"") { + Container(InternetBankingSystem__SinglePageApplication1__HASH30, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."", $tags=""Container Instance"") } } -Rel(InternetBankingSystem__APIApplication1__1f227f4, InternetBankingSystem__Database1__3296ca6, ""Reads from and writes to"", ""JDBC"") -Rel(InternetBankingSystem__SinglePageApplication1__bbe85d, InternetBankingSystem__APIApplication1__1f227f4, ""Makes API calls to"", ""JSON/HTTPS"") -Rel_Up(InternetBankingSystem__WebApplication1__28f79f6, InternetBankingSystem__SinglePageApplication1__bbe85d, ""Delivers to the customer's web browser"") +Rel(InternetBankingSystem__APIApplication1__HASH25, InternetBankingSystem__Database1__HASH28, ""Reads from and writes to"", ""JDBC"") +Rel(InternetBankingSystem__APIApplication1__HASH25, MainframeBankingSystem1__HASH20, ""Makes API calls to"", ""XML/HTTPS"") +Rel(InternetBankingSystem__SinglePageApplication1__HASH30, InternetBankingSystem__APIApplication1__HASH25, ""Makes API calls to"", ""JSON/HTTPS"") +Rel_Up(InternetBankingSystem__WebApplication1__HASH24, InternetBankingSystem__SinglePageApplication1__HASH30, ""Delivers to the customer's web browser"") + +SHOW_LEGEND() @enduml @startuml -!include -' C4_Deployment.puml is missing, simulate it with following definitions -' Scope: A single software system. -' Primary elements: Deployment nodes and containers within the software system in scope. -' Intended audience: Technical people inside and outside of the software development team; including software architects, developers and operations/support staff. - -' Colors -' ################################## -!define NODE_FONT_COLOR #444444 -!define NODE_BG_COLOR #FFFFFF - -' Styling -' ################################## - -skinparam rectangle<> { - Shadowing false - StereotypeFontSize 0 - FontColor NODE_FONT_COLOR - BackgroundColor NODE_BG_COLOR - BorderColor #444444 -} - -' Layout -' ################################## - -!definelong LAYOUT_WITH_LEGEND -hide stereotype -legend right -|= |= Type | -| | deployment node | -| | deployment container | -endlegend -!enddefinelong - -' Nodes -' ################################## -' PlantUML does not support automatic line breaks of container, if e_techn is very long insert line breaks with -' ""\n"" -!define Node(e_alias, e_label, e_techn) rectangle ""==e_label\n[e_techn]"" <> as e_alias +!include ' Structurizr.DeploymentView: LiveDeployment -title Internet Banking System - Deployment - -LAYOUT_WITH_LEGEND() - -Node(Deployment__Live__BigBankplc__3ffe15e, ""Big Bank plc"", ""Big Bank plc data center"") { - Node(Deployment__Live__BigBankplc__bigbankweb***__3f92e18, ""bigbank-web*** (x4)"", ""Ubuntu 16.04 LTS"") { - Node(Deployment__Live__BigBankplc__bigbankweb***__ApacheTomcat__27b4383, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { - Container(InternetBankingSystem__WebApplication2__1720850, ""Web Application"", ""Java and Spring MVC"", ""Delivers the static content and the Internet banking single page application."") +title Internet Banking System - Deployment - Live + +UpdateElementStyle(system, $bgColor = ""#1168bd"", $fontColor = ""#ffffff"", $borderColor = ""#1168bd"") +UpdateElementStyle(container, $bgColor = ""#438dd5"", $fontColor = ""#ffffff"", $borderColor = ""#438dd5"") +UpdateElementStyle(component, $bgColor = ""#85bbf0"", $fontColor = ""#000000"", $borderColor = ""#85bbf0"") +UpdateElementStyle(person, $bgColor = ""#08427b"", $fontColor = ""#ffffff"", $borderColor = ""#08427b"") +AddElementTag(Existing System, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") +AddElementTag(Bank Staff, $bgColor = ""#999999"", $fontColor = ""#ffffff"", $borderColor = ""#999999"") + +Node(BigBankplc__HASH31, ""Big Bank plc"", ""Big Bank plc data center"") { + Node(bigbankprod001__HASH32, ""bigbank-prod001"") { + System(MainframeBankingSystem1__HASH33, ""Mainframe Banking System"", ""Stores all of the core banking information about customers, accounts, transactions, etc."", $tags=""Software System Instance"") + } + Node(bigbankweb***__HASH34, ""bigbank-web*** (x4)"", ""Ubuntu 16.04 LTS"") { + Node(ApacheTomcat__HASH35, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { + Container(InternetBankingSystem__WebApplication1__HASH36, ""Web Application"", ""Java and Spring MVC"", ""Delivers the static content and the Internet banking single page application."", $tags=""Container Instance"") } } - Node(Deployment__Live__BigBankplc__bigbankapi***__263d9e8, ""bigbank-api*** (x8)"", ""Ubuntu 16.04 LTS"") { - Node(Deployment__Live__BigBankplc__bigbankapi***__ApacheTomcat__3b84ab, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { - Container(InternetBankingSystem__APIApplication2__1408a33, ""API Application"", ""Java and Spring MVC"", ""Provides Internet banking functionality via a JSON/HTTPS API."") + Node(bigbankapi***__HASH37, ""bigbank-api*** (x8)"", ""Ubuntu 16.04 LTS"") { + Node(ApacheTomcat__HASH38, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { + Container(InternetBankingSystem__APIApplication1__HASH39, ""API Application"", ""Java and Spring MVC"", ""Provides Internet banking functionality via a JSON/HTTPS API."", $tags=""Container Instance"") } } - Node(Deployment__Live__BigBankplc__bigbankdb01__35ec592, ""bigbank-db01"", ""Ubuntu 16.04 LTS"") { - Node(Deployment__Live__BigBankplc__bigbankdb01__OraclePrimary__19fd8f, ""Oracle - Primary"", ""Oracle 12c"") { - ContainerDb(InternetBankingSystem__Database2__1c974ec, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."") + Node(bigbankdb01__HASH40, ""bigbank-db01"", ""Ubuntu 16.04 LTS"") { + Node(OraclePrimary__HASH41, ""Oracle - Primary"", ""Oracle 12c"") { + ContainerDb(InternetBankingSystem__Database1__HASH42, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."", $tags=""Container Instance"") } } - Node(Deployment__Live__BigBankplc__bigbankdb02__1db08a2, ""bigbank-db02"", ""Ubuntu 16.04 LTS"") { - Node(Deployment__Live__BigBankplc__bigbankdb02__OracleSecondary__1c4ec22, ""Oracle - Secondary"", ""Oracle 12c"") { - ContainerDb(InternetBankingSystem__Database3__d89394, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."") + Node(bigbankdb02__HASH43, ""bigbank-db02"", ""Ubuntu 16.04 LTS"") { + Node(OracleSecondary__HASH44, ""Oracle - Secondary"", ""Oracle 12c"") { + ContainerDb(InternetBankingSystem__Database1__HASH45, ""Database"", ""Relational Database Schema"", ""Stores user registration information, hashed authentication credentials, access logs, etc."", $tags=""Failover+Container Instance"") } } } -Node(Deployment__Live__Customer'scomputer__2510bf3, ""Customer's computer"", ""Microsoft Windows or Apple \nmacOS"") { - Node(Deployment__Live__Customer'scomputer__WebBrowser__ba951, ""Web Browser"", ""Google Chrome, Mozilla \nFirefox, Apple Safari or \nMicrosoft Edge"") { - Container(InternetBankingSystem__SinglePageApplication2__298b31c, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."") +Node(Customer'scomputer__HASH46, ""Customer's computer"", ""Microsoft Windows or Apple macOS"") { + Node(WebBrowser__HASH47, ""Web Browser"", ""Chrome, Firefox, Safari, or Edge"") { + Container(InternetBankingSystem__SinglePageApplication1__HASH48, ""Single-Page Application"", ""JavaScript and Angular"", ""Provides all of the Internet banking functionality to customers via their web browser."", $tags=""Container Instance"") } } -Node(Deployment__Live__Customer'smobiledevice__1d6bcb6, ""Customer's mobile device"", ""Apple iOS or Android"") { - Container(InternetBankingSystem__MobileApp1__d004b3, ""Mobile App"", ""Xamarin"", ""Provides a limited subset of the Internet banking functionality to customers via their mobile device."") +Node(Customer'smobiledevice__HASH49, ""Customer's mobile device"", ""Apple iOS or Android"") { + Container(InternetBankingSystem__MobileApp1__HASH50, ""Mobile App"", ""Xamarin"", ""Provides a limited subset of the Internet banking functionality to customers via their mobile device."", $tags=""Container Instance"") } -Rel(InternetBankingSystem__APIApplication2__1408a33, InternetBankingSystem__Database2__1c974ec, ""Reads from and writes to"", ""JDBC"") -Rel(InternetBankingSystem__APIApplication2__1408a33, InternetBankingSystem__Database3__d89394, ""Reads from and writes to"", ""JDBC"") -Rel(InternetBankingSystem__MobileApp1__d004b3, InternetBankingSystem__APIApplication2__1408a33, ""Makes API calls to"", ""JSON/HTTPS"") -Rel(InternetBankingSystem__SinglePageApplication2__298b31c, InternetBankingSystem__APIApplication2__1408a33, ""Makes API calls to"", ""JSON/HTTPS"") -Rel_Up(InternetBankingSystem__WebApplication2__1720850, InternetBankingSystem__SinglePageApplication2__298b31c, ""Delivers to the customer's web browser"") -Rel_Left(Deployment__Live__BigBankplc__bigbankdb01__OraclePrimary__19fd8f, Deployment__Live__BigBankplc__bigbankdb02__OracleSecondary__1c4ec22, ""Replicates data to"") +Rel(InternetBankingSystem__APIApplication1__HASH39, InternetBankingSystem__Database1__HASH42, ""Reads from and writes to"", ""JDBC"") +Rel(InternetBankingSystem__APIApplication1__HASH39, InternetBankingSystem__Database1__HASH45, ""Reads from and writes to"", ""JDBC"", $tags=""Failover"") +Rel(InternetBankingSystem__APIApplication1__HASH39, MainframeBankingSystem1__HASH33, ""Makes API calls to"", ""XML/HTTPS"") +Rel(InternetBankingSystem__MobileApp1__HASH50, InternetBankingSystem__APIApplication1__HASH39, ""Makes API calls to"", ""JSON/HTTPS"") +Rel_Left(OraclePrimary__HASH41, OracleSecondary__HASH44, ""Replicates data to"") +Rel(InternetBankingSystem__SinglePageApplication1__HASH48, InternetBankingSystem__APIApplication1__HASH39, ""Makes API calls to"", ""JSON/HTTPS"") +Rel_Up(InternetBankingSystem__WebApplication1__HASH36, InternetBankingSystem__SinglePageApplication1__HASH48, ""Delivers to the customer's web browser"") + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -504,9 +320,9 @@ private void AddLayoutDetails(Workspace workspace) // all SystemLandscapeView, SystemContext, ... (update relation): // Rel_Up(EmailSystem, PersonalBankingCustomer, ""Sends e-mails to"") // Rel_Right(InternetBankingSystem, EmailSystem, ""Sends e-mail using"") - var emailSystem = workspace.Model.GetElementWithCanonicalName("/E-mail System"); - var personalBankingCustomer = workspace.Model.GetElementWithCanonicalName("/Personal Banking Customer"); - var internetBankingSystem = workspace.Model.GetElementWithCanonicalName("/Internet Banking System"); + var emailSystem = workspace.Model.GetElementWithCanonicalOrStaticalName("SoftwareSystem://E-mail System"); + var personalBankingCustomer = workspace.Model.GetElementWithCanonicalOrStaticalName("Person://Personal Banking Customer"); + var internetBankingSystem = workspace.Model.GetElementWithCanonicalOrStaticalName("SoftwareSystem://Internet Banking System"); var systemLandscapeView = workspace.Views.SystemLandscapeViews.First(); systemLandscapeView.Relationships @@ -519,7 +335,7 @@ private void AddLayoutDetails(Workspace workspace) .SetDirection(DirectionValues.Right); // but only SystemLandscapeView should use Down relations, therefore add the tags relation view specific (via AddViewTags) - var mainframeBankingSystem = workspace.Model.GetElementWithCanonicalName("/Mainframe Banking System"); + var mainframeBankingSystem = workspace.Model.GetElementWithCanonicalOrStaticalName("SoftwareSystem://Mainframe Banking System"); foreach (var relationshipView in systemLandscapeView.Relationships .Where(rv => rv.Relationship.DestinationId == mainframeBankingSystem.Id)) { @@ -534,31 +350,41 @@ private void AddLayoutDetails(Workspace workspace) .SetDirection(DirectionValues.Down); // DynamicView - // Rel_Right(InternetBankingSystem__SinglePageApplication, InternetBankingSystem__APIApplication__SignInController, ...) - // Rel_Right(InternetBankingSystem__APIApplication__SecurityComponent, InternetBankingSystem__Database, ...) + // RelIndex_Right(""1"", InternetBankingSystem__SinglePageApplication__1414c79, InternetBankingSystem__APIApplication__SignInController__22cc62b, ""Submits credentials to"", ""JSON/HTTPS"") + // RelIndex_Right(""3"", InternetBankingSystem__APIApplication__SecurityComponent__a4474, InternetBankingSystem__Database__18307f7, ""select * from users where username = ?"", ""JDBC"") + // Response switch displayed order - RelIndex_Left(""4"", InternetBankingSystem__Database__18307f7, InternetBankingSystem__APIApplication__SecurityComponent__a4474, ""Returns user data to"", ""JDBC"") + // Response switch displayed order - RelIndex_Left(""6"", InternetBankingSystem__APIApplication__SignInController__22cc62b, InternetBankingSystem__SinglePageApplication__1414c79, ""Sends back an authentication token to"", ""JSON/HTTPS"") var singlePageApplication = - workspace.Model.GetElementWithCanonicalName("/Internet Banking System/Single-Page Application"); + workspace.Model.GetElementWithCanonicalOrStaticalName("Container://Internet Banking System.Single-Page Application"); var signInController = - workspace.Model.GetElementWithCanonicalName("/Internet Banking System/API Application/Sign In Controller"); + workspace.Model.GetElementWithCanonicalOrStaticalName("Component://Internet Banking System.API Application.Sign In Controller"); var securityComponent = - workspace.Model.GetElementWithCanonicalName("/Internet Banking System/API Application/Security Component"); - var database = workspace.Model.GetElementWithCanonicalName("/Internet Banking System/Database") as Container; + workspace.Model.GetElementWithCanonicalOrStaticalName("Component://Internet Banking System.API Application.Security Component"); + var database = workspace.Model.GetElementWithCanonicalOrStaticalName("Container://Internet Banking System.Database") as Container; database.SetIsDatabase(true); var dynamicView = workspace.Views.DynamicViews.First(); dynamicView.Relationships - .First(r => r.Relationship.SourceId == singlePageApplication.Id && - r.Relationship.DestinationId == signInController.Id) + .First(r => + !(r.Response ?? false) && r.Relationship.SourceId == securityComponent.Id && r.Relationship.DestinationId == database.Id) .SetDirection(DirectionValues.Right); dynamicView.Relationships - .First(r => r.Relationship.SourceId == securityComponent.Id && r.Relationship.DestinationId == database.Id) + .First(r => + !(r.Response ?? false) && r.Relationship.SourceId == singlePageApplication.Id && r.Relationship.DestinationId == signInController.Id) .SetDirection(DirectionValues.Right); + // response swaps display order + dynamicView.Relationships + .First(r => + (r.Response ?? false) && r.Relationship.SourceId == securityComponent.Id && r.Relationship.DestinationId == database.Id) + .SetDirection(DirectionValues.Left); + dynamicView.Relationships + .First(r => + (r.Response ?? false) && r.Relationship.SourceId == singlePageApplication.Id && r.Relationship.DestinationId == signInController.Id) + .SetDirection(DirectionValues.Left); // ContainerView // Rel_Up(InternetBankingSystem__WebApplication, InternetBankingSystem__SinglePageApplication, "Delivers to the customer's web browser") - var apiApplication = workspace.Model.GetElementWithCanonicalName("/Internet Banking System/API Application"); - var webApplication = workspace.Model.GetElementWithCanonicalName("/Internet Banking System/Web Application"); - - + var apiApplication = workspace.Model.GetElementWithCanonicalOrStaticalName("Container://Internet Banking System.API Application"); + var webApplication = workspace.Model.GetElementWithCanonicalOrStaticalName("Container://Internet Banking System.Web Application"); var containerView = workspace.Views.ContainerViews.First(); containerView.Relationships .First(r => r.Relationship.SourceId == apiApplication.Id && @@ -584,47 +410,69 @@ private void AddLayoutDetails(Workspace workspace) // DeploymentView´(with already copied relations): DevelopmentDeployment, LiveDeployment // Rel_Up(InternetBankingSystem__WebApplication1, InternetBankingSystem__SinglePageApplication1, "Delivers to the customer's web browser") // Rel_Up(InternetBankingSystem__WebApplication2, InternetBankingSystem__SinglePageApplication2, "Delivers to the customer's web browser") - // Rel_Left(Deployment__Live__BigBankplc__bigbankdb01__OraclePrimary, Deployment__Live__BigBankplc__bigbankdb02__OracleSecondary, "Replicates data to") - var webApplication1 = workspace.Model.GetElementWithCanonicalName("/Internet Banking System/Web Application[1]"); - var webApplication2 = workspace.Model.GetElementWithCanonicalName("/Internet Banking System/Web Application[2]"); - var singlePageApplication1 = - workspace.Model.GetElementWithCanonicalName("/Internet Banking System/Single-Page Application[1]"); - var singlePageApplication2 = - workspace.Model.GetElementWithCanonicalName("/Internet Banking System/Single-Page Application[2]"); - var oraclePrimary = - workspace.Model.GetElementWithCanonicalName("/Deployment/Live/Big Bank plc/bigbank-db01/Oracle - Primary"); - var oracleSecondary = - workspace.Model.GetElementWithCanonicalName("/Deployment/Live/Big Bank plc/bigbank-db02/Oracle - Secondary"); + // Rel_Left(Live__BigBankplc__bigbankdb01__OraclePrimary, Live__BigBankplc__bigbankdb02__OracleSecondary, "Replicates data to") + + // Model is changed that instances are counted per parent orig ...[2] cannot be used anymore, separate per view, full names have to be used + var developmentWebApplication = + workspace.Model.GetElementWithCanonicalOrStaticalName("ContainerInstance://Development/Developer Laptop/Docker Container - Web Server/Apache Tomcat/Internet Banking System.Web Application[1]"); + var developmentSinglePageApplication = + workspace.Model.GetElementWithCanonicalOrStaticalName("ContainerInstance://Development/Developer Laptop/Web Browser/Internet Banking System.Single-Page Application[1]"); var developmentDeploymentView = workspace.Views.DeploymentViews.First(); - var liveDeploymentView = workspace.Views.DeploymentViews.Last(); developmentDeploymentView.Relationships - .First(r => r.Relationship.SourceId == webApplication1.Id && - r.Relationship.DestinationId == singlePageApplication1.Id) + .First(r => r.Relationship.SourceId == developmentWebApplication.Id && + r.Relationship.DestinationId == developmentSinglePageApplication.Id) .SetDirection(DirectionValues.Up); + + var liveWebApplication = + workspace.Model.GetElementWithCanonicalOrStaticalName("ContainerInstance://Live/Big Bank plc/bigbank-web***/Apache Tomcat/Internet Banking System.Web Application[1]"); + var liveSinglePageApplication = + workspace.Model.GetElementWithCanonicalOrStaticalName("ContainerInstance://Live/Customer's computer/Web Browser/Internet Banking System.Single-Page Application[1]"); + var liveOraclePrimary = + workspace.Model.GetElementWithCanonicalOrStaticalName("DeploymentNode://Live/Big Bank plc/bigbank-db01/Oracle - Primary"); + var liveOracleSecondary = + workspace.Model.GetElementWithCanonicalOrStaticalName("DeploymentNode://Live/Big Bank plc/bigbank-db02/Oracle - Secondary"); + var liveDeploymentView = workspace.Views.DeploymentViews.Last(); liveDeploymentView.Relationships - .First(r => r.Relationship.SourceId == webApplication2.Id && - r.Relationship.DestinationId == singlePageApplication2.Id) + .First(r => r.Relationship.SourceId == liveWebApplication.Id && + r.Relationship.DestinationId == liveSinglePageApplication.Id) .SetDirection(DirectionValues.Up); liveDeploymentView.Relationships - .First(r => r.Relationship.SourceId == oraclePrimary.Id && r.Relationship.DestinationId == oracleSecondary.Id) + .First(r => r.Relationship.SourceId == liveOraclePrimary.Id && r.Relationship.DestinationId == liveOracleSecondary.Id) .SetDirection(DirectionValues.Left); + + // !!! structrizr has another border color calculation (if no value is set) details unclear; set explicit border color too + workspace.Views.Configuration.Styles.Elements + .Where(e=>!string.IsNullOrWhiteSpace(e.Background) && string.IsNullOrWhiteSpace(e.Stroke)).ToList() + .ForEach(e=>e.Stroke = e.Background); + + /* add/update other styles + workspace.Views.Configuration.Styles.Add(new ElementStyle("Container Instance"){ Background = "#E0E0C0", Stroke= "#E0E0C0", Color = "#000000"}); + workspace.Views.Configuration.Styles.Elements.Where(e=>e.Tag=="Failover").ToList() + .ForEach(e=> { e.Background = "#808080"; e.Stroke="#808080"; e.Color = "#FFFFFF"; }); + + workspace.Views.Configuration.Styles.Add(new RelationshipStyle("Relationship"){ Color = "#000000" }); + workspace.Views.Configuration.Styles.Relationships.Where(r=>r.Tag=="Failover").ToList() + .ForEach(r=> { r.Color = "#808080"; r.Dashed = true; }); + */ } [Fact] - public void test_writeWorkspace_WithCustomBaseUrl() + public void test_writeWorkspace_WithNextFeaturesCustomBaseUrl() { PopulateWorkspace(); - _plantUMLWriter.CustomBaseUrl = @"https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/"; + _plantUMLWriter.CustomBaseUrl = @"https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/"; + _plantUMLWriter.EnableNextFeatures = true; _plantUMLWriter.Write(_workspace, _stringWriter); Assert.Equal( @"@startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Context.puml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Context.puml ' Structurizr.SystemLandscapeView: enterpriseContext title System Landscape for Some Enterprise -LAYOUT_WITH_LEGEND() +SHOW_PERSON_OUTLINE() +AddRelTag(""Back"", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine()) System_Ext(EmailSystem__1127701, ""E-mail System"") Enterprise_Boundary(SomeEnterprise, ""Some Enterprise"") { @@ -634,15 +482,18 @@ public void test_writeWorkspace_WithCustomBaseUrl() Rel(EmailSystem__1127701, User__387cc75, ""Delivers e-mails to"") Rel(SoftwareSystem__31d545b, EmailSystem__1127701, ""Sends e-mail using"") Rel(User__387cc75, SoftwareSystem__31d545b, ""Uses"") + +SHOW_LEGEND() @enduml @startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Context.puml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Context.puml ' Structurizr.SystemContextView: systemContext title Software System - System Context -LAYOUT_WITH_LEGEND() +SHOW_PERSON_OUTLINE() +AddRelTag(""Back"", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine()) Enterprise_Boundary(SomeEnterprise, ""Some Enterprise"") { System_Ext(EmailSystem__1127701, ""E-mail System"") @@ -652,15 +503,18 @@ title Software System - System Context Rel(SoftwareSystem__31d545b, EmailSystem__1127701, ""Sends e-mail using"") Rel(User__387cc75, SoftwareSystem__31d545b, ""Uses"") } + +SHOW_LEGEND() @enduml @startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Container.puml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Container.puml ' Structurizr.ContainerView: containers title Software System - Containers -LAYOUT_WITH_LEGEND() +SHOW_PERSON_OUTLINE() +AddRelTag(""Back"", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine()) System_Ext(EmailSystem__1127701, ""E-mail System"") Person(User__387cc75, ""User"") @@ -672,15 +526,18 @@ title Software System - Containers Rel(User__387cc75, SoftwareSystem__WebApplication__d2a342, """", ""HTTP"") Rel(SoftwareSystem__WebApplication__d2a342, SoftwareSystem__Database__39bccb8, ""Reads from and writes to"", ""JDBC"") Rel(SoftwareSystem__WebApplication__d2a342, EmailSystem__1127701, ""Sends e-mail using"") + +SHOW_LEGEND() @enduml @startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Component.puml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Component.puml ' Structurizr.ComponentView: components title Software System - Web Application - Components -LAYOUT_WITH_LEGEND() +SHOW_PERSON_OUTLINE() +AddRelTag(""Back"", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine()) ContainerDb(SoftwareSystem__Database__39bccb8, ""Database"", ""Relational Database Schema"", ""Stores information"") System_Ext(EmailSystem__1127701, ""E-mail System"") @@ -696,15 +553,18 @@ title Software System - Web Application - Components Rel(SoftwareSystem__WebApplication__SomeController__341621c, SoftwareSystem__WebApplication__SomeRepository__6d9009, ""Uses"") Rel(SoftwareSystem__WebApplication__SomeRepository__6d9009, SoftwareSystem__Database__39bccb8, ""Reads from and writes to"", ""JDBC"") Rel(User__387cc75, SoftwareSystem__WebApplication__SomeController__341621c, ""Uses"", ""HTTP"") + +SHOW_LEGEND() @enduml @startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Dynamic.puml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Dynamic.puml ' Structurizr.DynamicView: dynamic title Web Application - Dynamic -LAYOUT_WITH_LEGEND() +SHOW_PERSON_OUTLINE() +AddRelTag(""Back"", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine()) ContainerDb(SoftwareSystem__Database__39bccb8, ""Database"", ""Relational Database Schema"", ""Stores information"") Person(User__387cc75, ""User"") @@ -712,30 +572,35 @@ title Web Application - Dynamic Component(SoftwareSystem__WebApplication__SomeController__341621c, ""SomeController"", ""Spring MVC Controller"") Component(SoftwareSystem__WebApplication__SomeRepository__6d9009, ""SomeRepository"", ""Spring Data"") } -Interact2(""1"", User__387cc75, SoftwareSystem__WebApplication__SomeController__341621c, ""Requests /something"", ""HTTP"") -Interact2(""2"", SoftwareSystem__WebApplication__SomeController__341621c, SoftwareSystem__WebApplication__SomeRepository__6d9009, """") -Interact2(""3"", SoftwareSystem__WebApplication__SomeRepository__6d9009, SoftwareSystem__Database__39bccb8, ""select * from something"", ""JDBC"") +RelIndex(""1"", User__387cc75, SoftwareSystem__WebApplication__SomeController__341621c, ""Requests /something"", ""HTTP"") +RelIndex(""2"", SoftwareSystem__WebApplication__SomeController__341621c, SoftwareSystem__WebApplication__SomeRepository__6d9009, """") +RelIndex(""3"", SoftwareSystem__WebApplication__SomeRepository__6d9009, SoftwareSystem__Database__39bccb8, ""select * from something"", ""JDBC"") + +SHOW_LEGEND() @enduml @startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Deployment.puml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Deployment.puml ' Structurizr.DeploymentView: deployment -title Software System - Deployment +title Software System - Deployment - Default -LAYOUT_WITH_LEGEND() +SHOW_PERSON_OUTLINE() +AddRelTag(""Back"", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine()) -Node(Deployment__Default__DatabaseServer__1edef6c, ""Database Server"", ""Ubuntu 12.04 LTS"") { - Node(Deployment__Default__DatabaseServer__MySQL__1fa4f18, ""MySQL"", ""MySQL 5.5.x"") { - ContainerDb(SoftwareSystem__Database1__bb9c73, ""Database"", ""Relational Database Schema"", ""Stores information"") +Node(DatabaseServer__1edef6c, ""Database Server"", ""Ubuntu 12.04 LTS"") { + Node(MySQL__1fa4f18, ""MySQL"", ""MySQL 5.5.x"") { + ContainerDb(SoftwareSystem__Database1__bb9c73, ""Database"", ""Relational Database Schema"", ""Stores information"", $tags=""Container Instance"") } } -Node(Deployment__Default__WebServer__1e2ffe, ""Web Server"", ""Ubuntu 12.04 LTS"") { - Node(Deployment__Default__WebServer__ApacheTomcat__2b8afb4, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { - Container(SoftwareSystem__WebApplication1__31f1f25, ""Web Application"", ""Java and spring MVC"", ""Delivers content"") +Node(WebServer__1e2ffe, ""Web Server"", ""Ubuntu 12.04 LTS"") { + Node(ApacheTomcat__2b8afb4, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { + Container(SoftwareSystem__WebApplication1__31f1f25, ""Web Application"", ""Java and spring MVC"", ""Delivers content"", $tags=""Container Instance"") } } Rel(SoftwareSystem__WebApplication1__31f1f25, SoftwareSystem__Database1__bb9c73, ""Reads from and writes to"", ""JDBC"") + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -747,7 +612,7 @@ public void test_writeEnterpriseContextView() PopulateWorkspace(); SystemLandscapeView systemLandscapeView = _workspace.Views.SystemLandscapeViews.First(); - _plantUMLWriter.Write(systemLandscapeView, _stringWriter); + _plantUMLWriter.Write(systemLandscapeView, _workspace.Views.Configuration, _stringWriter); Assert.Equal( @"@startuml @@ -756,8 +621,6 @@ public void test_writeEnterpriseContextView() ' Structurizr.SystemLandscapeView: enterpriseContext title System Landscape for Some Enterprise -LAYOUT_WITH_LEGEND() - System_Ext(EmailSystem__1934cbe, ""E-mail System"") Enterprise_Boundary(SomeEnterprise, ""Some Enterprise"") { Person(User__3b843b5, ""User"") @@ -766,6 +629,8 @@ public void test_writeEnterpriseContextView() Rel(EmailSystem__1934cbe, User__3b843b5, ""Delivers e-mails to"") Rel(SoftwareSystem__7134f, EmailSystem__1934cbe, ""Sends e-mail using"") Rel(User__3b843b5, SoftwareSystem__7134f, ""Uses"") + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -778,18 +643,16 @@ public void test_writeEnterpriseContextView_WithCustomBaseUrl() PopulateWorkspace(); SystemLandscapeView systemLandscapeView = _workspace.Views.SystemLandscapeViews.First(); - _plantUMLWriter.CustomBaseUrl = @"https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/"; - _plantUMLWriter.Write(systemLandscapeView, _stringWriter); + _plantUMLWriter.CustomBaseUrl = @"https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/"; + _plantUMLWriter.Write(systemLandscapeView, _workspace.Views.Configuration, _stringWriter); Assert.Equal( @"@startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Context.puml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Context.puml ' Structurizr.SystemLandscapeView: enterpriseContext title System Landscape for Some Enterprise -LAYOUT_WITH_LEGEND() - System_Ext(EmailSystem__1934cbe, ""E-mail System"") Enterprise_Boundary(SomeEnterprise, ""Some Enterprise"") { Person(User__3b843b5, ""User"") @@ -798,6 +661,8 @@ public void test_writeEnterpriseContextView_WithCustomBaseUrl() Rel(EmailSystem__1934cbe, User__3b843b5, ""Delivers e-mails to"") Rel(SoftwareSystem__7134f, EmailSystem__1934cbe, ""Sends e-mail using"") Rel(User__3b843b5, SoftwareSystem__7134f, ""Uses"") + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -810,7 +675,7 @@ public void test_writeSystemContextView() PopulateWorkspace(); SystemContextView systemContextView = _workspace.Views.SystemContextViews.First(); - _plantUMLWriter.Write(systemContextView, _stringWriter); + _plantUMLWriter.Write(systemContextView, _workspace.Views.Configuration, _stringWriter); Assert.Equal( @"@startuml @@ -819,8 +684,6 @@ public void test_writeSystemContextView() ' Structurizr.SystemContextView: systemContext title Software System - System Context -LAYOUT_WITH_LEGEND() - Enterprise_Boundary(SomeEnterprise, ""Some Enterprise"") { System_Ext(EmailSystem__1127701, ""E-mail System"") System(SoftwareSystem__31d545b, ""Software System"") @@ -829,6 +692,8 @@ title Software System - System Context Rel(SoftwareSystem__31d545b, EmailSystem__1127701, ""Sends e-mail using"") Rel(User__387cc75, SoftwareSystem__31d545b, ""Uses"") } + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -840,7 +705,7 @@ public void test_writeContainerView() PopulateWorkspace(); ContainerView containerView = _workspace.Views.ContainerViews.First(); - _plantUMLWriter.Write(containerView, _stringWriter); + _plantUMLWriter.Write(containerView, _workspace.Views.Configuration, _stringWriter); Assert.Equal( @"@startuml @@ -849,8 +714,6 @@ public void test_writeContainerView() ' Structurizr.ContainerView: containers title Software System - Containers -LAYOUT_WITH_LEGEND() - System_Ext(EmailSystem__1934cbe, ""E-mail System"") Person(User__3b843b5, ""User"") System_Boundary(SoftwareSystem__7134f, ""Software System"") { @@ -861,6 +724,8 @@ title Software System - Containers Rel(User__3b843b5, SoftwareSystem__WebApplication__1cc1659, """", ""HTTP"") Rel(SoftwareSystem__WebApplication__1cc1659, SoftwareSystem__Database__270f9f2, ""Reads from and writes to"", ""JDBC"") Rel(SoftwareSystem__WebApplication__1cc1659, EmailSystem__1934cbe, ""Sends e-mail using"") + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -872,7 +737,7 @@ public void test_writeComponentsView() PopulateWorkspace(); ComponentView componentView = _workspace.Views.ComponentViews.First(); - _plantUMLWriter.Write(componentView, _stringWriter); + _plantUMLWriter.Write(componentView, _workspace.Views.Configuration, _stringWriter); Assert.Equal( @"@startuml @@ -881,8 +746,6 @@ public void test_writeComponentsView() ' Structurizr.ComponentView: components title Software System - Web Application - Components -LAYOUT_WITH_LEGEND() - ContainerDb(SoftwareSystem__Database__270f9f2, ""Database"", ""Relational Database Schema"", ""Stores information"") System_Ext(EmailSystem__1934cbe, ""E-mail System"") Person(User__3b843b5, ""User"") @@ -897,6 +760,8 @@ title Software System - Web Application - Components Rel(SoftwareSystem__WebApplication__SomeController__327a713, SoftwareSystem__WebApplication__SomeRepository__23f6823, ""Uses"") Rel(SoftwareSystem__WebApplication__SomeRepository__23f6823, SoftwareSystem__Database__270f9f2, ""Reads from and writes to"", ""JDBC"") Rel(User__3b843b5, SoftwareSystem__WebApplication__SomeController__327a713, ""Uses"", ""HTTP"") + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -908,197 +773,27 @@ public void test_writeDynamicView() PopulateWorkspace(); DynamicView dynamicView = _workspace.Views.DynamicViews.First(); - _plantUMLWriter.Write(dynamicView, _stringWriter); + _plantUMLWriter.Write(dynamicView, _workspace.Views.Configuration, _stringWriter); // Dynamic diagrams can be drawn with Components Assert.Equal( @"@startuml -!include -' C4_Dynamic.puml is missing, simulate it with following definitions -' Scope: Interactions in an enterprise, software system or container. -' Primary and supporting elements: Depends on the diagram scope - -' enterprise - people and software systems related to the enterprise in scope -' software system - see system context or container diagrams, -' container - see component diagram. -' Intended audience: Technical and non-technical people, inside and outside of the software development team. - -' Dynamic diagram introduces (automatically) numbered interactions: -' Interact(): used automatic calculated index, -' Interact2(): index can be explicit defined, -' SetIndex(): set the next index, -' GetIndex(): get the index and automatically increase index - -' Index -' ################################## - -!function $inc_($value, $step=1) - !return $value + $step -!endfunction - -!$index=1 - -!function SetIndex($new_index) - !$index=$new_index -!endfunction - -!function GetIndex($auto_increase=1) - !$old = $index - !$index=$inc_($index, $auto_increase) - !return $old -!endfunction - -' Interact -' ################################## -!define Interact2(e_index, e_from, e_to, e_label) Rel(e_from, e_to, ""e_index: e_label"") -!define Interact2(e_index, e_from, e_to, e_label, e_techn) Rel(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_Back(e_index, e_from, e_to, e_label) Rel_Back(e_from, e_to, ""e_index: e_label"") -!define Interact2_Back(e_index, e_from, e_to, e_label, e_techn) Rel_Back(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_Neighbor(e_index, e_from, e_to, e_label) Rel_Neighbor(e_from, e_to, ""e_index: e_label"") -!define Interact2_Neighbor(e_index, e_from, e_to, e_label, e_techn) Rel_Neighbor(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_Back_Neighbor(e_index, e_from, e_to, e_label) Rel_Back_Neighbor(e_from, e_to, ""e_index: e_label"") -!define Interact2_Back_Neighbor(e_index, e_from, e_to, e_label, e_techn) Rel_Back_Neighbor(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_D(e_index, e_from, e_to, e_label) Rel_D(e_from, e_to, ""e_index: e_label"") -!define Interact2_D(e_index, e_from, e_to, e_label, e_techn) Rel_D(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Down(e_index, e_from, e_to, e_label) Rel_Down(e_from, e_to, ""e_index: e_label"") -!define Interact2_Down(e_index, e_from, e_to, e_label, e_techn) Rel_Down(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_U(e_index, e_from, e_to, e_label) Rel_U(e_from, e_to, ""e_index: e_label"") -!define Interact2_U(e_index, e_from, e_to, e_label, e_techn) Rel_U(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Up(e_index, e_from, e_to, e_label) Rel_Up(e_from, e_to, ""e_index: e_label"") -!define Interact2_Up(e_index, e_from, e_to, e_label, e_techn) Rel_Up(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_L(e_index, e_from, e_to, e_label) Rel_L(e_from, e_to, ""e_index: e_label"") -!define Interact2_L(e_index, e_from, e_to, e_label, e_techn) Rel_L(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Left(e_index, e_from, e_to, e_label) Rel_Left(e_from, e_to, ""e_index: e_label"") -!define Interact2_Left(e_index, e_from, e_to, e_label, e_techn) Rel_Left(e_from, e_to, ""e_index: e_label"", e_techn) - -!define Interact2_R(e_index, e_from, e_to, e_label) Rel_R(e_from, e_to, ""e_index: e_label"") -!define Interact2_R(e_index, e_from, e_to, e_label, e_techn) Rel_R(e_from, e_to, ""e_index: e_label"", e_techn) -!define Interact2_Right(e_index, e_from, e_to, e_label) Rel_Right(e_from, e_to, ""e_index: e_label"") -!define Interact2_Right(e_index, e_from, e_to, e_label, e_techn) Rel_Right(e_from, e_to, ""e_index: e_label"", e_techn) - -!unquoted function Interact($e_from, $e_to, $e_label) - Interact2($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact($e_from, $e_to, $e_label, $e_techn) - Interact2($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_Back($e_from, $e_to, $e_label) - Interact2_Back($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Back($e_from, $e_to, $e_label, $e_techn) - Interact2_Back($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_Neighbor($e_from, $e_to, $e_label) - Interact2_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Neighbor($e_from, $e_to, $e_label, $e_techn) - Interact2_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_Back_Neighbor($e_from, $e_to, $e_label) - Interact2_Back_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Back_Neighbor($e_from, $e_to, $e_label, $e_techn) - Interact2_Back_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_D($e_from, $e_to, $e_label) - Interact2_D($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_D($e_from, $e_to, $e_label, $e_techn) - Interact2_D($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Down($e_from, $e_to, $e_label) - Interact2_Down($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Down($e_from, $e_to, $e_label, $e_techn) - Interact2_Down($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_U($e_from, $e_to, $e_label) - Interact2_U($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_U($e_from, $e_to, $e_label, $e_techn) - Interact2_U($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Up($e_from, $e_to, $e_label) - Interact2_Up($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Up($e_from, $e_to, $e_label, $e_techn) - Interact2_Up($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_L($e_from, $e_to, $e_label) - Interact2_L($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_L($e_from, $e_to, $e_label, $e_techn) - Interact2_L($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Left($e_from, $e_to, $e_label) - Interact2_Left($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Left($e_from, $e_to, $e_label, $e_techn) - Interact2_Left($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction - -!unquoted function Interact_R($e_from, $e_to, $e_label) - Interact2_R($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_R($e_from, $e_to, $e_label, $e_techn) - Interact2_R($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Right($e_from, $e_to, $e_label) - Interact2_Right($index, ""$e_from"", ""$e_to"", ""$e_label"") - !$index=$inc_($index) -!endfunction -!unquoted function Interact_Right($e_from, $e_to, $e_label, $e_techn) - Interact2_Right($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn) - !$index=$inc_($index) -!endfunction +!include ' Structurizr.DynamicView: dynamic title Web Application - Dynamic -LAYOUT_WITH_LEGEND() - ContainerDb(SoftwareSystem__Database__39bccb8, ""Database"", ""Relational Database Schema"", ""Stores information"") Person(User__387cc75, ""User"") Container_Boundary(SoftwareSystem__WebApplication__d2a342, ""Web Application"") { Component(SoftwareSystem__WebApplication__SomeController__341621c, ""SomeController"", ""Spring MVC Controller"") Component(SoftwareSystem__WebApplication__SomeRepository__6d9009, ""SomeRepository"", ""Spring Data"") } -Interact2(""1"", User__387cc75, SoftwareSystem__WebApplication__SomeController__341621c, ""Requests /something"", ""HTTP"") -Interact2(""2"", SoftwareSystem__WebApplication__SomeController__341621c, SoftwareSystem__WebApplication__SomeRepository__6d9009, """") -Interact2(""3"", SoftwareSystem__WebApplication__SomeRepository__6d9009, SoftwareSystem__Database__39bccb8, ""select * from something"", ""JDBC"") +RelIndex(""1"", User__387cc75, SoftwareSystem__WebApplication__SomeController__341621c, ""Requests /something"", ""HTTP"") +RelIndex(""2"", SoftwareSystem__WebApplication__SomeController__341621c, SoftwareSystem__WebApplication__SomeRepository__6d9009, """") +RelIndex(""3"", SoftwareSystem__WebApplication__SomeRepository__6d9009, SoftwareSystem__Database__39bccb8, ""select * from something"", ""JDBC"") + +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); @@ -1110,106 +805,33 @@ public void test_writeDeploymentView() PopulateWorkspace(); DeploymentView deploymentView = _workspace.Views.DeploymentViews.First(); - _plantUMLWriter.Write(deploymentView, _stringWriter); + _plantUMLWriter.Write(deploymentView, _workspace.Views.Configuration, _stringWriter); Assert.Equal( @"@startuml -!include -' C4_Deployment.puml is missing, simulate it with following definitions -' Scope: A single software system. -' Primary elements: Deployment nodes and containers within the software system in scope. -' Intended audience: Technical people inside and outside of the software development team; including software architects, developers and operations/support staff. - -' Colors -' ################################## -!define NODE_FONT_COLOR #444444 -!define NODE_BG_COLOR #FFFFFF - -' Styling -' ################################## - -skinparam rectangle<> { - Shadowing false - StereotypeFontSize 0 - FontColor NODE_FONT_COLOR - BackgroundColor NODE_BG_COLOR - BorderColor #444444 -} - -' Layout -' ################################## - -!definelong LAYOUT_WITH_LEGEND -hide stereotype -legend right -|= |= Type | -| | deployment node | -| | deployment container | -endlegend -!enddefinelong - -' Nodes -' ################################## -' PlantUML does not support automatic line breaks of container, if e_techn is very long insert line breaks with -' ""\n"" -!define Node(e_alias, e_label, e_techn) rectangle ""==e_label\n[e_techn]"" <> as e_alias +!include ' Structurizr.DeploymentView: deployment -title Software System - Deployment - -LAYOUT_WITH_LEGEND() +title Software System - Deployment - Default -Node(Deployment__Default__DatabaseServer__1edef6c, ""Database Server"", ""Ubuntu 12.04 LTS"") { - Node(Deployment__Default__DatabaseServer__MySQL__1fa4f18, ""MySQL"", ""MySQL 5.5.x"") { - ContainerDb(SoftwareSystem__Database1__bb9c73, ""Database"", ""Relational Database Schema"", ""Stores information"") +Node(DatabaseServer__1edef6c, ""Database Server"", ""Ubuntu 12.04 LTS"") { + Node(MySQL__1fa4f18, ""MySQL"", ""MySQL 5.5.x"") { + ContainerDb(SoftwareSystem__Database1__bb9c73, ""Database"", ""Relational Database Schema"", ""Stores information"", $tags=""Container Instance"") } } -Node(Deployment__Default__WebServer__1e2ffe, ""Web Server"", ""Ubuntu 12.04 LTS"") { - Node(Deployment__Default__WebServer__ApacheTomcat__2b8afb4, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { - Container(SoftwareSystem__WebApplication1__31f1f25, ""Web Application"", ""Java and spring MVC"", ""Delivers content"") +Node(WebServer__1e2ffe, ""Web Server"", ""Ubuntu 12.04 LTS"") { + Node(ApacheTomcat__2b8afb4, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { + Container(SoftwareSystem__WebApplication1__31f1f25, ""Web Application"", ""Java and spring MVC"", ""Delivers content"", $tags=""Container Instance"") } } Rel(SoftwareSystem__WebApplication1__31f1f25, SoftwareSystem__Database1__bb9c73, ""Reads from and writes to"", ""JDBC"") -@enduml -".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); - } - - [Fact] - public void test_writeDeploymentView_WithCustomBaseUrl() - { - PopulateWorkspace(); - DeploymentView deploymentView = _workspace.Views.DeploymentViews.First(); - - _plantUMLWriter.CustomBaseUrl = @"https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/"; - _plantUMLWriter.Write(deploymentView, _stringWriter); - - Assert.Equal( -@"@startuml -!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/C4_Deployment.puml - -' Structurizr.DeploymentView: deployment -title Software System - Deployment - -LAYOUT_WITH_LEGEND() - -Node(Deployment__Default__DatabaseServer__1edef6c, ""Database Server"", ""Ubuntu 12.04 LTS"") { - Node(Deployment__Default__DatabaseServer__MySQL__1fa4f18, ""MySQL"", ""MySQL 5.5.x"") { - ContainerDb(SoftwareSystem__Database1__bb9c73, ""Database"", ""Relational Database Schema"", ""Stores information"") - } -} -Node(Deployment__Default__WebServer__1e2ffe, ""Web Server"", ""Ubuntu 12.04 LTS"") { - Node(Deployment__Default__WebServer__ApacheTomcat__2b8afb4, ""Apache Tomcat"", ""Apache Tomcat 8.x"") { - Container(SoftwareSystem__WebApplication1__31f1f25, ""Web Application"", ""Java and spring MVC"", ""Delivers content"") - } -} -Rel(SoftwareSystem__WebApplication1__31f1f25, SoftwareSystem__Database1__bb9c73, ""Reads from and writes to"", ""JDBC"") +SHOW_LEGEND() @enduml ".UnifyNewLine().UnifyHashValues(), _stringWriter.ToString().UnifyHashValues()); } - private void PopulateWorkspace() { Model model = _workspace.Model; diff --git a/Structurizr.PlantUML.Tests/IO/PlantUML/PlantUMLWriterTests.cs b/Structurizr.PlantUML.Tests/IO/PlantUML/PlantUMLWriterTests.cs index 3d5e4c7..754debe 100644 --- a/Structurizr.PlantUML.Tests/IO/PlantUML/PlantUMLWriterTests.cs +++ b/Structurizr.PlantUML.Tests/IO/PlantUML/PlantUMLWriterTests.cs @@ -101,7 +101,7 @@ title Web Application - Dynamic @enduml @startuml -title Software System - Deployment +title Software System - Deployment - Default node ""Database Server"" <> as 23 { node ""MySQL"" <> as 24 { artifact ""Database"" <> as 25 @@ -254,7 +254,7 @@ public void test_writeDeploymentView() Assert.Equal( @"@startuml -title Software System - Deployment +title Software System - Deployment - Default node ""Database Server"" <> as 23 { node ""MySQL"" <> as 24 { artifact ""Database"" <> as 25 diff --git a/Structurizr.PlantUML.Tests/Structurizr.PlantUML.Tests.csproj b/Structurizr.PlantUML.Tests/Structurizr.PlantUML.Tests.csproj index 1152688..99d79e0 100644 --- a/Structurizr.PlantUML.Tests/Structurizr.PlantUML.Tests.csproj +++ b/Structurizr.PlantUML.Tests/Structurizr.PlantUML.Tests.csproj @@ -1,6 +1,6 @@  - netcoreapp1.1 + netcoreapp2.1 false diff --git a/Structurizr.PlantUML/IO/C4PlantUML/C4PlantUmlException.cs b/Structurizr.PlantUML/IO/C4PlantUML/C4PlantUmlException.cs new file mode 100644 index 0000000..71ba6d4 --- /dev/null +++ b/Structurizr.PlantUML/IO/C4PlantUML/C4PlantUmlException.cs @@ -0,0 +1,9 @@ +using System; + +namespace Structurizr.PlantUML.IO.C4PlantUML +{ + public class C4PlantUmlException : Exception + { + public C4PlantUmlException(string message) : base(message) { } + } +} diff --git a/Structurizr.PlantUML/IO/C4PlantUML/C4PlantUmlWriter.cs b/Structurizr.PlantUML/IO/C4PlantUML/C4PlantUmlWriter.cs index 186f336..51cadb9 100644 --- a/Structurizr.PlantUML/IO/C4PlantUML/C4PlantUmlWriter.cs +++ b/Structurizr.PlantUML/IO/C4PlantUML/C4PlantUmlWriter.cs @@ -2,10 +2,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Structurizr.IO.C4PlantUML.ModelExtensions; // Source base version copied from https://gist.github.com/coldacid/465fa8f3a4cd3fdd7b640a65ad5b86f4 (https://github.com/structurizr/dotnet/issues/47) // kirchsth: Extended with dynamic and deployment view +// kirchsth: updated to update generated source to new C4PlantUml stdlib v2.2.0 (no additional dynamic and deployment view macros are required anymore, calls updated) +// kirchsth: Support ViewConfiguration, tags and styles +// kirchsth: next planed C4PlantUml stdlib v2.3.0 features can be used with CustomBaseUrl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/ namespace Structurizr.IO.C4PlantUML { public class C4PlantUmlWriter : PlantUMLWriterBase @@ -23,18 +27,22 @@ public enum LayoutDirection public LayoutDirection? Layout { get; set; } /// - /// PlantUML-stdlib or https://raw.githubusercontent.com/RicardoNiepel/C4-PlantUML/release/1-0/ does not support - /// dynamic or deployment diagrams. They can be used via the PlantUML-stdlib and in the diagram added definitions - /// or use a pull-request version which is available at https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/ + /// C4PlantUml stdlib v2.2.0 () supports dynamic or deployment diagrams. They can be used via the PlantUML-stdlib and no + /// special CustomBaseUrl is required. + /// Only next stdlib features (like Person shapes) has to be defined via CustomBaseUrl=https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/ /// (if the value is empty/null then PlantUML-stdlib with added definitions is used) /// - public string CustomBaseUrl { get; set; } = ""; // @"https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/"; + public string CustomBaseUrl { get; set; } =""; // @"https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/"; + public bool EnableNextFeatures { get; set; } = false; // true; - protected override void Write(SystemLandscapeView view, TextWriter writer) + public string AdditionalProlog { get; set; } + public string AdditionalEpilog { get; set; } + + protected override void Write(SystemLandscapeView view, ViewConfiguration viewConfiguration, TextWriter writer) { var showBoundary = view.EnterpriseBoundaryVisible ?? true; - WriteProlog(view, writer); + WriteProlog(view, viewConfiguration, writer); view.Elements .Select(ev => ev.Element) @@ -71,14 +79,14 @@ protected override void Write(SystemLandscapeView view, TextWriter writer) Write(view.Relationships, writer); - WriteEpilog(view, writer); + WriteEpilog(view, viewConfiguration, writer); } - protected override void Write(SystemContextView view, TextWriter writer) + protected override void Write(SystemContextView view, ViewConfiguration viewConfiguration, TextWriter writer) { var showBoundary = view.EnterpriseBoundaryVisible ?? true; - WriteProlog(view, writer); + WriteProlog(view, viewConfiguration, writer); if (showBoundary) { @@ -95,17 +103,17 @@ protected override void Write(SystemContextView view, TextWriter writer) if (showBoundary) writer.WriteLine("}"); - WriteEpilog(view, writer); + WriteEpilog(view, viewConfiguration, writer); } - protected override void Write(ContainerView view, TextWriter writer) + protected override void Write(ContainerView view, ViewConfiguration viewConfiguration, TextWriter writer) { var externals = view.Elements .Select(ev => ev.Element) .Where(e => !(e is Container)); var showBoundary = externals.Any(); - WriteProlog(view, writer); + WriteProlog(view, viewConfiguration, writer); externals .OrderBy(e => e.Name).ToList() @@ -125,10 +133,10 @@ protected override void Write(ContainerView view, TextWriter writer) Write(view.Relationships, writer); - WriteEpilog(view, writer); + WriteEpilog(view, viewConfiguration, writer); } - protected override void Write(ComponentView view, TextWriter writer) + protected override void Write(ComponentView view, ViewConfiguration viewConfiguration, TextWriter writer) { var nonComponents = view.Elements .Select(ev => ev.Element) @@ -140,7 +148,7 @@ from ev in view.Elements group e by e.Parent; var showBoundary = nonComponents.Any() || nonContainedComponents.Any(); - WriteProlog(view, writer); + WriteProlog(view, viewConfiguration, writer); nonComponents .OrderBy(e => e.Name).ToList() @@ -171,12 +179,12 @@ from ev in view.Elements Write(view.Relationships, writer); - WriteEpilog(view, writer); + WriteEpilog(view, viewConfiguration, writer); } - protected override void Write(DynamicView view, TextWriter writer) + protected override void Write(DynamicView view, ViewConfiguration viewConfiguration, TextWriter writer) { - WriteProlog(view, writer); + WriteProlog(view, viewConfiguration, writer); IList innerElements = new List(); IList outerElements = new List(); @@ -224,12 +232,12 @@ protected override void Write(DynamicView view, TextWriter writer) WriteDynamicInteractions(view.Relationships, writer); - WriteEpilog(view, writer); + WriteEpilog(view, viewConfiguration, writer); } - protected override void Write(DeploymentView view, TextWriter writer) + protected override void Write(DeploymentView view, ViewConfiguration viewConfiguration, TextWriter writer) { - WriteProlog(view, writer); + WriteProlog(view, viewConfiguration, writer); view.Elements .Where(ev => ev.Element is DeploymentNode && ev.Element.Parent == null) @@ -239,7 +247,7 @@ protected override void Write(DeploymentView view, TextWriter writer) Write(view.Relationships, writer); - WriteEpilog(view, writer); + WriteEpilog(view, viewConfiguration, writer); } private void Write(DeploymentNode deploymentNode, TextWriter writer, int indentLevel) @@ -258,6 +266,11 @@ private void Write(DeploymentNode deploymentNode, TextWriter writer, int indentL Write(containerInstance, writer, indentLevel + 1); } + foreach (SoftwareSystemInstance systemInstance in deploymentNode.SoftwareSystemInstances) + { + Write(systemInstance, writer, indentLevel + 1); + } + writer.WriteLine($"{indent}}}"); } @@ -296,6 +309,11 @@ private string TypeOf(Element e) return "Container"; } + if (e is SoftwareSystemInstance) + { + return "Software System"; + } + return ""; } @@ -305,275 +323,60 @@ private bool HasValue(string s) return s != null && s.Trim().Length > 0; } - protected override void WriteProlog(View view, TextWriter writer) + protected override void WriteProlog(View view, ViewConfiguration viewConfiguration, TextWriter writer) { + if (view == null) throw new ArgumentNullException(nameof(view)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + writer.WriteLine("@startuml"); + string diagramType; + HashSet existingLegendTags; // (already mapped) tags (styles) which have to be overwritten not added + switch (view) { case SystemLandscapeView _: case SystemContextView _: - writer.WriteLine(!string.IsNullOrWhiteSpace(CustomBaseUrl) - ? $"!includeurl {CustomBaseUrl}C4_Context.puml" - : $"!include "); + diagramType = "Context"; + existingLegendTags = new HashSet { "person", "system", "external_person", "external_system" }; break; - case ComponentView _: - writer.WriteLine(!string.IsNullOrWhiteSpace(CustomBaseUrl) - ? $"!includeurl {CustomBaseUrl}C4_Component.puml" - : $"!include "); + case ContainerView _: + diagramType = "Container"; + existingLegendTags = new HashSet { "person", "system", "container", "external_person", "external_system", "external_container" }; break; case DynamicView _: - if (!string.IsNullOrWhiteSpace(CustomBaseUrl)) - { - writer.WriteLine($"!includeurl {CustomBaseUrl}C4_Dynamic.puml"); - } - else - { - writer.WriteLine(@"!include "); - // Add missing deployment nodes (until they are part of the plantuml macros) - writer.WriteLine(@"' C4_Dynamic.puml is missing, simulate it with following definitions"); - - writer.WriteLine(@"' Scope: Interactions in an enterprise, software system or container."); - writer.WriteLine(@"' Primary and supporting elements: Depends on the diagram scope - "); - writer.WriteLine(@"' enterprise - people and software systems related to the enterprise in scope "); - writer.WriteLine(@"' software system - see system context or container diagrams, "); - writer.WriteLine(@"' container - see component diagram."); - writer.WriteLine(@"' Intended audience: Technical and non-technical people, inside and outside of the software development team."); - writer.WriteLine(@""); - writer.WriteLine(@"' Dynamic diagram introduces (automatically) numbered interactions: "); - writer.WriteLine(@"' Interact(): used automatic calculated index, "); - writer.WriteLine(@"' Interact2(): index can be explicit defined,"); - writer.WriteLine(@"' SetIndex(): set the next index, "); - writer.WriteLine(@"' GetIndex(): get the index and automatically increase index"); - writer.WriteLine(@""); - writer.WriteLine(@"' Index"); - writer.WriteLine(@"' ##################################"); - writer.WriteLine(@""); - writer.WriteLine(@"!function $inc_($value, $step=1)"); - writer.WriteLine(@" !return $value + $step"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!$index=1"); - writer.WriteLine(@""); - writer.WriteLine(@"!function SetIndex($new_index)"); - writer.WriteLine(@" !$index=$new_index"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!function GetIndex($auto_increase=1)"); - writer.WriteLine(@" !$old = $index"); - writer.WriteLine(@" !$index=$inc_($index, $auto_increase)"); - writer.WriteLine(@" !return $old"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"' Interact"); - writer.WriteLine(@"' ##################################"); - writer.WriteLine(@"!define Interact2(e_index, e_from, e_to, e_label) Rel(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2(e_index, e_from, e_to, e_label, e_techn) Rel(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!define Interact2_Back(e_index, e_from, e_to, e_label) Rel_Back(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_Back(e_index, e_from, e_to, e_label, e_techn) Rel_Back(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!define Interact2_Neighbor(e_index, e_from, e_to, e_label) Rel_Neighbor(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_Neighbor(e_index, e_from, e_to, e_label, e_techn) Rel_Neighbor(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!define Interact2_Back_Neighbor(e_index, e_from, e_to, e_label) Rel_Back_Neighbor(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_Back_Neighbor(e_index, e_from, e_to, e_label, e_techn) Rel_Back_Neighbor(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!define Interact2_D(e_index, e_from, e_to, e_label) Rel_D(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_D(e_index, e_from, e_to, e_label, e_techn) Rel_D(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@"!define Interact2_Down(e_index, e_from, e_to, e_label) Rel_Down(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_Down(e_index, e_from, e_to, e_label, e_techn) Rel_Down(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!define Interact2_U(e_index, e_from, e_to, e_label) Rel_U(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_U(e_index, e_from, e_to, e_label, e_techn) Rel_U(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@"!define Interact2_Up(e_index, e_from, e_to, e_label) Rel_Up(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_Up(e_index, e_from, e_to, e_label, e_techn) Rel_Up(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!define Interact2_L(e_index, e_from, e_to, e_label) Rel_L(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_L(e_index, e_from, e_to, e_label, e_techn) Rel_L(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@"!define Interact2_Left(e_index, e_from, e_to, e_label) Rel_Left(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_Left(e_index, e_from, e_to, e_label, e_techn) Rel_Left(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!define Interact2_R(e_index, e_from, e_to, e_label) Rel_R(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_R(e_index, e_from, e_to, e_label, e_techn) Rel_R(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@"!define Interact2_Right(e_index, e_from, e_to, e_label) Rel_Right(e_from, e_to, ""e_index: e_label"")"); - writer.WriteLine(@"!define Interact2_Right(e_index, e_from, e_to, e_label, e_techn) Rel_Right(e_from, e_to, ""e_index: e_label"", e_techn)"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact_Back($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_Back($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Back($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_Back($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact_Neighbor($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Neighbor($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact_Back_Neighbor($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_Back_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Back_Neighbor($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_Back_Neighbor($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact_D($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_D($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_D($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_D($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Down($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_Down($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Down($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_Down($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact_U($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_U($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_U($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_U($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Up($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_Up($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Up($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_Up($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact_L($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_L($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_L($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_L($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Left($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_Left($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Left($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_Left($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@""); - writer.WriteLine(@"!unquoted function Interact_R($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_R($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_R($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_R($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Right($e_from, $e_to, $e_label) "); - writer.WriteLine(@" Interact2_Right($index, ""$e_from"", ""$e_to"", ""$e_label"")"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - writer.WriteLine(@"!unquoted function Interact_Right($e_from, $e_to, $e_label, $e_techn) "); - writer.WriteLine(@" Interact2_Right($index, ""$e_from"", ""$e_to"", ""$e_label"", $e_techn)"); - writer.WriteLine(@" !$index=$inc_($index)"); - writer.WriteLine(@"!endfunction"); - } + diagramType = "Dynamic"; + existingLegendTags = new HashSet { "person", "system", "container", "component", "external_person", "external_system", "external_container", "external_component" }; break; case DeploymentView _: - if (!string.IsNullOrWhiteSpace(CustomBaseUrl)) - { - writer.WriteLine($"!includeurl {CustomBaseUrl}C4_Deployment.puml"); - } - else - { - writer.WriteLine(@"!include "); - // Add missing deployment nodes (until they are part of the plantuml macros) - writer.WriteLine(@"' C4_Deployment.puml is missing, simulate it with following definitions"); - - writer.WriteLine(@"' Scope: A single software system."); - writer.WriteLine(@"' Primary elements: Deployment nodes and containers within the software system in scope."); - writer.WriteLine(@"' Intended audience: Technical people inside and outside of the software development team; including software architects, developers and operations/support staff."); - writer.WriteLine(@""); - writer.WriteLine(@"' Colors"); - writer.WriteLine(@"' ##################################"); - writer.WriteLine(@"!define NODE_FONT_COLOR #444444"); - writer.WriteLine(@"!define NODE_BG_COLOR #FFFFFF"); - writer.WriteLine(@""); - writer.WriteLine(@"' Styling"); - writer.WriteLine(@"' ##################################"); - writer.WriteLine(@""); - writer.WriteLine(@"skinparam rectangle<> {"); - writer.WriteLine(@" Shadowing false"); - writer.WriteLine(@" StereotypeFontSize 0"); - writer.WriteLine(@" FontColor NODE_FONT_COLOR"); - writer.WriteLine(@" BackgroundColor NODE_BG_COLOR"); - writer.WriteLine(@" BorderColor #444444"); - writer.WriteLine(@"}"); - writer.WriteLine(@""); - writer.WriteLine(@"' Layout"); - writer.WriteLine(@"' ##################################"); - writer.WriteLine(@""); - writer.WriteLine(@"!definelong LAYOUT_WITH_LEGEND"); - writer.WriteLine(@"hide stereotype"); - writer.WriteLine(@"legend right"); - writer.WriteLine(@"|= |= Type |"); - writer.WriteLine(@"| | deployment node |"); - writer.WriteLine(@"| | deployment container |"); - writer.WriteLine(@"endlegend"); - writer.WriteLine(@"!enddefinelong"); - writer.WriteLine(@""); - writer.WriteLine(@"' Nodes"); - writer.WriteLine(@"' ##################################"); - writer.WriteLine(@"' PlantUML does not support automatic line breaks of container, if e_techn is very long insert line breaks with "); - writer.WriteLine(@"' ""\n"""); - writer.WriteLine(@"!define Node(e_alias, e_label, e_techn) rectangle ""==e_label\n[e_techn]"" <> as e_alias"); - } + diagramType = "Deployment"; + existingLegendTags = new HashSet { "person", "system", "container", "external_person", "external_system", "external_container", "node" }; break; default: - writer.WriteLine(!string.IsNullOrWhiteSpace(CustomBaseUrl) - ? $"!includeurl {CustomBaseUrl}C4_Container.puml" - : $"!include "); // as long no stdlib is used the Component diagram definition can be reused + diagramType = "Component"; + existingLegendTags = new HashSet { "person", "system", "container", "component", "external_person", "external_system", "external_container", "external_component" }; break; } + writer.WriteLine(!string.IsNullOrWhiteSpace(CustomBaseUrl) + ? $"!includeurl {CustomBaseUrl}C4_{diagramType}.puml" + : $"!include "); + writer.WriteLine(); writer.WriteLine($"' {view.GetType()}: {view.Key}"); writer.WriteLine("title " + GetTitle(view)); writer.WriteLine(); - if (LayoutWithLegend) - writer.WriteLine("LAYOUT_WITH_LEGEND()"); // C4 PlantUML workaround add () if (LayoutAsSketch) - writer.WriteLine("LAYOUT_AS_SKETCH()"); // C4 PlantUML workaround add () + writer.WriteLine("LAYOUT_AS_SKETCH()"); + + if (EnableNextFeatures) + writer.WriteLine("SHOW_PERSON_OUTLINE()"); + if (Layout.HasValue) { switch (Layout) @@ -588,10 +391,154 @@ protected override void WriteProlog(View view, TextWriter writer) throw new InvalidOperationException($"Unknown {nameof(LayoutDirection)} value"); } } - if (LayoutWithLegend || LayoutAsSketch || Layout.HasValue) + if (LayoutAsSketch || Layout.HasValue) + writer.WriteLine(); + + WriteExistingStyles(view, existingLegendTags, viewConfiguration, writer); + + if (!string.IsNullOrWhiteSpace(AdditionalProlog)) + writer.WriteLine(AdditionalProlog); + } + + protected override void WriteEpilog(View view, ViewConfiguration viewConfiguration, TextWriter writer) + { + if (view == null) throw new ArgumentNullException(nameof(view)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + if (!string.IsNullOrWhiteSpace(AdditionalEpilog)) + writer.WriteLine(AdditionalEpilog); + + if (LayoutWithLegend) + { + writer.WriteLine(); + writer.WriteLine("SHOW_LEGEND()"); // C4 PlantUML workaround add () + } + + writer.WriteLine("@enduml"); + writer.WriteLine(); + } + + protected virtual void WriteExistingStyles(View view, HashSet existingLegendTags, ViewConfiguration viewConfiguration, TextWriter writer) + { + if (view == null) throw new ArgumentNullException(nameof(view)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + ElementStyle baseES = viewConfiguration.Styles.Elements.FirstOrDefault(es => es.Tag == "Element"); + RelationshipStyle definedRS = viewConfiguration.Styles.Relationships.FirstOrDefault(rs => rs.Tag == "Relationship"); + + if (EnableNextFeatures) // linestyle + { + // add Back related style (which is typically dotted in Structurizr) (and if defined then it will be overwritten with viewConfiguration.Styles.Relationships) + writer.WriteLine("AddRelTag(\"Back\", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine())"); + writer.WriteLine(); + } + + foreach (var es in viewConfiguration.Styles.Elements) + if (es != baseES) // skip Element + Write(es, baseES, existingLegendTags, writer); + foreach (var rs in viewConfiguration.Styles.Relationships) + Write(rs, definedRS, writer); + + if (viewConfiguration.Styles.Elements.Count > 0 || viewConfiguration.Styles.Relationships.Count > 0) writer.WriteLine(); } + protected virtual void Write(ElementStyle es, ElementStyle baseElementStyle, HashSet existingLegendTags, TextWriter writer) + { + var defined = StructurizrTags2DiagramTags.TryGetValue(es.Tag, out var diagramTag); + if (!defined) + diagramTag = es.Tag; + + // UpdateElementStyle or AddElementTag(elementName, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape) // ?shadowing not used; ?shape only rounded or eight-sided + var allArgs = new StringBuilder(); + WriteColor("$bgColor", es.Background, baseElementStyle?.Background, false, allArgs); + WriteColor("$fontColor", es.Color, baseElementStyle?.Color, false, allArgs); + WriteColor("$borderColor", es.Stroke, baseElementStyle?.Stroke, false, allArgs); + if (EnableNextFeatures) + { + // default shape of element is ignored + WriteShape(es.Shape, allArgs); + } + + if (allArgs.Length > 0) + { + writer.Write(defined ? "UpdateElementStyle" : "AddElementTag"); + writer.WriteLine($"({diagramTag}{allArgs})"); + } + } + + protected virtual void Write(RelationshipStyle rs, RelationshipStyle definedRelationshipStyle, TextWriter writer) + { + // only "Relationship" is predefined (which is defaultRelationshipStyle) + var diagramTag = rs.Tag; + var defined = (rs == definedRelationshipStyle); + + // UpdateRelStyle or AddRelTag(tagStereo, ?textColor, ?lineColor, ?lineStyle) + var allArgs = new StringBuilder(); + WriteColor("$textColor", rs.Color, definedRelationshipStyle?.Color, defined, allArgs); + WriteColor("$lineColor", rs.Color, definedRelationshipStyle?.Color, defined, allArgs); + if (!defined) + { + if (EnableNextFeatures) + { + if (rs.Dashed == true) // C# Structurize does not support all styles + allArgs.Append($", $lineStyle = DashedLine()"); + } + } + + if (allArgs.Length > 0) + { + writer.Write(defined ? "UpdateRelStyle(" : $"AddRelTag({diagramTag}, "); + writer.WriteLine($"{allArgs.Remove(0, 2)})"); // remove first ", " + } + } + + protected void WriteColor(string argName, string elementColor, string defaultColor, bool lineColorsRequired, StringBuilder allArgs) + { + var color = elementColor; + if (string.IsNullOrWhiteSpace(color)) + color = defaultColor; + + if (!string.IsNullOrWhiteSpace(color)) + allArgs.Append($", {argName} = \"{color}\""); + else if (lineColorsRequired) + allArgs.Append($", {argName} = $ARROW_COLOR"); + } + + protected void WriteShape(Shape elementShape, StringBuilder allArgs) + { + if (EnableNextFeatures) + { + switch (elementShape) + { + case Shape.RoundedBox: + allArgs.Append($", $shape = RoundedBoxShape()"); + break; + case Shape.Hexagon: + allArgs.Append($", $shape = EightSidedShape()"); + break; + default: + // all other ignored atm (Database handled via ..Db() extension) + break; + } + } + } + + protected static Dictionary StructurizrTags2DiagramTags = new Dictionary + { + // Element is handled via defaultElementStyle and is not added as tag + ["Element"] = "", + ["Person"] = "person", + ["Software System"] = "system", + ["Container"] = "container", + ["Component"] = "component", + ["Deployment Node"] = "node" + // ?? how should this tags be mapped -> reused without special mapping atm + // ["Infrastructure Node"] = "", + // ["Software System Instance"] = "", + // ["Container Instance"] = "", + }; + protected virtual void Write(Element element, TextWriter writer, int indentLevel = 0, bool asBoundary = false) { var indent = indentLevel == 0 ? "" : new string(' ', indentLevel * 2); @@ -617,10 +564,6 @@ protected virtual void Write(Element element, TextWriter writer, int indentLevel macro = "Node"; title = deploymentNode.Name + (deploymentNode.Instances > 1 ? $" (x{deploymentNode.Instances})" : ""); technology = deploymentNode.Technology; - // PlantUML supports no automatic line breaks of titles, if it belongs to a surrounding object - // make workaround with html tags (they are not working via multiple lines too) - if (technology.Length > 30) - technology = BlockText(technology, 30, @"\n"); break; default: throw new NotSupportedException($"{element.GetType()} not supported boundary type"); @@ -659,6 +602,12 @@ protected virtual void Write(Element element, TextWriter writer, int indentLevel technology = cmp.Technology ?? ""; isDatabase = cmp.GetIsDatabase(); break; + case SoftwareSystemInstance sysIn: + macro = "System"; + title = sysIn.SoftwareSystem.Name; + description = sysIn.SoftwareSystem.Description; + external = sysIn.SoftwareSystem.Location == Location.External; + break; case ContainerInstance cntIn: macro = "Container"; title = cntIn.Container.Name; @@ -687,9 +636,20 @@ protected virtual void Write(Element element, TextWriter writer, int indentLevel { writer.Write($", \"{EscapeText(description)}\""); } + WriteTags(element, writer); writer.WriteLine(")"); } + private void WriteTags(Element element, TextWriter writer) + { + var tags = element.GetAllTags().Where(t => !StructurizrTags2DiagramTags.ContainsKey(t)).Reverse().ToList(); + if (tags.Count > 0) + { + var combinedTags = string.Join("+", tags); + writer.Write($", $tags=\"{EscapeText(combinedTags)}\""); + } + } + protected virtual void Write(ISet relationships, TextWriter writer) { relationships @@ -706,14 +666,37 @@ protected virtual void Write(RelationshipView relationshipView, TextWriter write label = advancedDescription ?? relationship.Description ?? "", tech = !string.IsNullOrWhiteSpace(relationship.Technology) ? relationship.Technology : null; + if (relationshipView.Response == true) + { + var swap = source; + source = dest; + dest = swap; + } + var macro = GetSpecificLayoutMacro(relationshipView); writer.Write($"{macro}({source}, {dest}, \"{EscapeText(label)}\""); if (tech != null) writer.Write($", \"{EscapeText(tech)}\""); + WriteTags(relationshipView, writer); writer.WriteLine(")"); } + private void WriteTags(RelationshipView relationshipView, TextWriter writer) + { + var relationship = relationshipView.Relationship; + var tags = new List(); + if (relationshipView.Response == true) + tags.Add("Back"); + + tags.AddRange(relationship.GetAllTags().Where(t => t != "Relationship").Reverse()); + if (tags.Count > 0) + { + var combinedTags = string.Join("+", tags); + writer.Write($", $tags=\"{EscapeText(combinedTags)}\""); + } + } + protected virtual void WriteDynamicInteractions(ISet relationships, TextWriter writer) { relationships @@ -729,12 +712,20 @@ protected virtual void WriteDynamicInteraction(RelationshipView relationshipView dest = TokenizeName(relationship.Destination), tech = !string.IsNullOrWhiteSpace(relationship.Technology) ? relationship.Technology : null; + if (relationshipView.Response == true) + { + var swap = source; + source = dest; + dest = swap; + } + var macro = GetSpecificLayoutMacro(relationshipView); - macro = "Interact2" + macro.Substring("Rel".Length); + macro = "RelIndex" + macro.Substring("Rel".Length); writer.Write($"{macro}(\"{order}\", {source}, {dest}, \"{EscapeText(label)}\""); if (tech != null) writer.Write($", \"{EscapeText(tech)}\""); + WriteTags(relationshipView, writer); writer.WriteLine(")"); } diff --git a/Structurizr.PlantUML/IO/C4PlantUML/IPlantUMLWriter.cs b/Structurizr.PlantUML/IO/C4PlantUML/IPlantUMLWriter.cs index 525e008..5b47e65 100644 --- a/Structurizr.PlantUML/IO/C4PlantUML/IPlantUMLWriter.cs +++ b/Structurizr.PlantUML/IO/C4PlantUML/IPlantUMLWriter.cs @@ -2,11 +2,12 @@ // Source base version copied from https://gist.github.com/coldacid/465fa8f3a4cd3fdd7b640a65ad5b86f4 (https://github.com/structurizr/dotnet/issues/47) // kirchsth: Extended with dynamic and deployment view +// kirchsth: Support ViewConfiguration, tags and styles namespace Structurizr.IO.C4PlantUML { public interface IPlantUMLWriter { void Write(Workspace workspace, TextWriter writer); - void Write(View view, TextWriter writer); + void Write(View view, ViewConfiguration viewConfiguration, TextWriter writer); } } \ No newline at end of file diff --git a/Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/ModelExtensions.cs b/Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/ModelExtensions.cs new file mode 100644 index 0000000..4d02454 --- /dev/null +++ b/Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/ModelExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using Structurizr.PlantUML.IO.C4PlantUML; + +namespace Structurizr.IO.C4PlantUML.ModelExtensions +{ + public static class ModelExtensions + { + /// + /// new impl. of CanonicalName starts with "{ElementType}://" or "{ElementType}://{DeploymentName}/{DeploymentName}/" instead of "/" therefore it can be optionally ignored + /// Additional / in staticNames have to be converted to . + /// + /// the canonical name with elementType prefix (e.g. Container://SoftwareSystem/Container) or without elementType prefix (e.g. /SoftwareSystem/Container) + /// + public static Element GetElementWithCanonicalOrStaticalName(this Model model, string canonicalName, bool compareOnlyLastPart=true) + { + if (string.IsNullOrWhiteSpace(canonicalName)) + throw new ArgumentException("A canonical name must be specified."); + var found = model.GetElements().FirstOrDefault((Func) (x => + { + if (compareOnlyLastPart) + return x.CanonicalName.EndsWith(canonicalName); + else + return x.CanonicalName == canonicalName; + })); + + if (found == null) + { + var all = model.GetElements().Select(e => e.CanonicalName).ToList(); + var combined = string.Join("\n", all); + throw new C4PlantUmlException( + $"Element {canonicalName} could not be found. Following elements exist:\n{combined}"); + } + return found; + } + } +} \ No newline at end of file diff --git a/Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/RelationshipViewExtensions.cs b/Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/RelationshipViewExtensions.cs index 1d4e309..39decbd 100644 --- a/Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/RelationshipViewExtensions.cs +++ b/Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/RelationshipViewExtensions.cs @@ -1,9 +1,15 @@ +using System.Collections.Generic; + namespace Structurizr.IO.C4PlantUML.ModelExtensions { + /// + /// WORKAROUND: RelationshipView supports no properties anymore, therefore the direction is stored in Position + /// public static class RelationshipViewExtensions { /// /// Get a direction of the relation which should be used in a specific C4PlantUML views + /// (direction is stored in Position) /// /// /// returns true if it is defined via the view specific RelationshipView and false if it is defined via the underlying Relationship @@ -11,7 +17,7 @@ public static class RelationshipViewExtensions public static string GetDirection(this RelationshipView relationshipView, out bool viewSpecific) { string value = DirectionValues.NotSet; - if (relationshipView.Properties?.TryGetValue(Properties.Direction, out value) == true) + if (relationshipView.Position.HasValue && Position2Direction.TryGetValue(relationshipView.Position.Value, out value) == true) { viewSpecific = true; return value; @@ -24,15 +30,32 @@ public static string GetDirection(this RelationshipView relationshipView, out bo /// /// Set a direction of the relation which should be used in a specific C4PlantUML views + /// (direction is internal stored in Position) /// /// /// one of public static void SetDirection(this RelationshipView relationshipView, string direction) { if (string.IsNullOrWhiteSpace(direction)) // direction DirectionValues.NotSet - relationshipView.Properties.Remove(Properties.Direction); + relationshipView.Position = null; else - relationshipView.Properties[Properties.Direction] = direction; + relationshipView.Position = Direction2Position[direction]; } + + private static Dictionary Direction2Position = new Dictionary + { + [DirectionValues.Up] = 1, + [DirectionValues.Down] = 2, + [DirectionValues.Left] = 3, + [DirectionValues.Right] = 4 + }; + + private static Dictionary Position2Direction = new Dictionary + { + [1] = DirectionValues.Up, + [2] = DirectionValues.Down, + [3] = DirectionValues.Left, + [4] = DirectionValues.Right + }; } } \ No newline at end of file diff --git a/Structurizr.PlantUML/IO/C4PlantUML/PlantUMLWriterBase.cs b/Structurizr.PlantUML/IO/C4PlantUML/PlantUMLWriterBase.cs index ee2285e..d8d2a96 100644 --- a/Structurizr.PlantUML/IO/C4PlantUML/PlantUMLWriterBase.cs +++ b/Structurizr.PlantUML/IO/C4PlantUML/PlantUMLWriterBase.cs @@ -5,6 +5,8 @@ // Source base version copied from https://gist.github.com/coldacid/465fa8f3a4cd3fdd7b640a65ad5b86f4 (https://github.com/structurizr/dotnet/issues/47) // kirchsth: Extended with dynamic and deployment view +// kirchsth: Support ViewConfiguration, tags and styles + namespace Structurizr.IO.C4PlantUML { /// @@ -25,16 +27,17 @@ public void Write(Workspace workspace, TextWriter writer) CurrentViewModel = workspace.Model; - workspace.Views.SystemLandscapeViews.ToList().ForEach(v => Write(v, writer)); - workspace.Views.SystemContextViews.ToList().ForEach(v => Write(v, writer)); - workspace.Views.ContainerViews.ToList().ForEach(v => Write(v, writer)); - workspace.Views.ComponentViews.ToList().ForEach(v => Write(v, writer)); - workspace.Views.DynamicViews.ToList().ForEach(v => Write(v, writer)); - workspace.Views.DeploymentViews.ToList().ForEach(v => Write(v, writer)); + var viewConfiguration = workspace.Views.Configuration; + workspace.Views.SystemLandscapeViews.ToList().ForEach(v => Write(v, viewConfiguration, writer)); + workspace.Views.SystemContextViews.ToList().ForEach(v => Write(v, viewConfiguration, writer)); + workspace.Views.ContainerViews.ToList().ForEach(v => Write(v, viewConfiguration, writer)); + workspace.Views.ComponentViews.ToList().ForEach(v => Write(v, viewConfiguration, writer)); + workspace.Views.DynamicViews.ToList().ForEach(v => Write(v, viewConfiguration, writer)); + workspace.Views.DeploymentViews.ToList().ForEach(v => Write(v, viewConfiguration, writer)); } /// - public void Write(View view, TextWriter writer) + public void Write(View view, ViewConfiguration viewConfiguration, TextWriter writer) { if (view == null) throw new ArgumentNullException(nameof(view)); if (writer == null) throw new ArgumentNullException(nameof(writer)); @@ -44,22 +47,22 @@ public void Write(View view, TextWriter writer) switch (view) { case SystemLandscapeView sl: - Write(sl, writer); + Write(sl, viewConfiguration, writer); break; case SystemContextView sc: - Write(sc, writer); + Write(sc, viewConfiguration, writer); break; case ContainerView ct: - Write(ct, writer); + Write(ct, viewConfiguration, writer); break; case ComponentView cp: - Write(cp, writer); + Write(cp, viewConfiguration, writer); break; case DynamicView dy: - Write(dy, writer); + Write(dy, viewConfiguration, writer); break; case DeploymentView de: - Write(de, writer); + Write(de, viewConfiguration, writer); break; default: throw new NotSupportedException($"{view.GetType()} not supported for export"); @@ -71,42 +74,42 @@ public void Write(View view, TextWriter writer) /// /// /// - protected abstract void Write(SystemLandscapeView view, TextWriter writer); + protected abstract void Write(SystemLandscapeView view, ViewConfiguration viewConfiguration, TextWriter writer); /// /// Writes a system context view in PlantUML format to the provided writer. /// /// /// - protected abstract void Write(SystemContextView view, TextWriter writer); + protected abstract void Write(SystemContextView view, ViewConfiguration viewConfiguration, TextWriter writer); /// /// Writes a container view in PlantUML format to the provided writer. /// /// /// - protected abstract void Write(ContainerView view, TextWriter writer); + protected abstract void Write(ContainerView view, ViewConfiguration viewConfiguration, TextWriter writer); /// /// Writes a component view in PlantUML format to the provided writer. /// /// /// - protected abstract void Write(ComponentView view, TextWriter writer); + protected abstract void Write(ComponentView view, ViewConfiguration viewConfiguration, TextWriter writer); /// /// Writes a dynamic view in PlantUML format to the provided writer. /// /// /// - protected abstract void Write(DynamicView view, TextWriter writer); + protected abstract void Write(DynamicView view, ViewConfiguration viewConfiguration, TextWriter writer); /// /// Writes a deployment view in PlantUML format to the provided writer. /// /// /// - protected abstract void Write(DeploymentView view, TextWriter writer); + protected abstract void Write(DeploymentView view, ViewConfiguration viewConfiguration, TextWriter writer); /// /// Produces a standard PlantUML diagram prolog for the provided view. @@ -115,7 +118,7 @@ public void Write(View view, TextWriter writer) /// /// is . /// is . - protected virtual void WriteProlog(View view, TextWriter writer) + protected virtual void WriteProlog(View view, ViewConfiguration viewConfiguration, TextWriter writer) { if (view == null) throw new ArgumentNullException(nameof(view)); if (writer == null) throw new ArgumentNullException(nameof(writer)); @@ -132,7 +135,7 @@ protected virtual void WriteProlog(View view, TextWriter writer) /// /// is . /// is . - protected virtual void WriteEpilog(View view, TextWriter writer) + protected virtual void WriteEpilog(View view, ViewConfiguration viewConfiguration, TextWriter writer) { if (view == null) throw new ArgumentNullException(nameof(view)); if (writer == null) throw new ArgumentNullException(nameof(writer)); @@ -165,13 +168,22 @@ protected string TokenizeName(string s, int? hash = null) { if (String.IsNullOrWhiteSpace(s)) return ""; - s = s - .Trim('/') - .Replace(" ", "") - .Replace("-", "") - .Replace("[", "") - .Replace("]", "") - .Replace("/", "__"); + // canonically name calculation changed + // a) instead of "/" starts with "{ElementType}://"; remove it that it is compatible with old impl. + // b) deployment namespaces are added with "/"; remove it that it is shorter (unique parts created via hash) + // c) orig "/" in static namespaces replaced with "."; replace with "__" that it is compatible with old impl. + var p = s.LastIndexOf('/'); + if (p >= 0) + s = s.Substring(p + 1); + + s = s.Replace(" ", "") + .Replace("-", "") + .Replace("[", "") + .Replace("]", "") + .Replace("(", "") + .Replace(")", "") + .Replace(".", "__"); + if (hash.HasValue) { s = s + "__" + hash.Value.ToString("x"); @@ -191,45 +203,6 @@ protected virtual string GetTitle(View view) => ? String.IsNullOrWhiteSpace(view.Title) ? view.Name : view.Title : throw new ArgumentNullException(nameof(view)); - protected string BlockText(string s, int blockWidth, string formattedLineBreak) - { - var block = s; - - if (blockWidth > 0 && !s.Contains("\n") && !s.Contains("\r")) - { - var formatted = new StringBuilder(); - int pos = 0; - string word = ""; - - foreach (var c in s) - { - word += c; - if (c == ' ') - { - if (pos != 0 && pos + word.Length > blockWidth) - { - formatted.Append(formattedLineBreak); - pos = 0; - } - formatted.Append(word); - pos += word.Length; - word = ""; - } - } - - if (word.Length > 0) - { - if (pos != 0 && pos + word.Length > blockWidth) - formatted.Append(formattedLineBreak); - formatted.Append(word); - } - - block = formatted.ToString(); - } - - return block; - } - protected string EscapeText(string s) => s.Replace("\"", """); } } \ No newline at end of file diff --git a/Structurizr.PlantUML/Structurizr.PlantUML.csproj b/Structurizr.PlantUML/Structurizr.PlantUML.csproj index 8c3e4da..2bb6043 100644 --- a/Structurizr.PlantUML/Structurizr.PlantUML.csproj +++ b/Structurizr.PlantUML/Structurizr.PlantUML.csproj @@ -16,11 +16,11 @@ - netstandard1.3;net45 + netstandard2.0;net45 - + \ No newline at end of file diff --git a/Structurizr.Reflection.Examples/Structurizr.Reflection.Examples.csproj b/Structurizr.Reflection.Examples/Structurizr.Reflection.Examples.csproj index 8be178a..a143cdf 100644 --- a/Structurizr.Reflection.Examples/Structurizr.Reflection.Examples.csproj +++ b/Structurizr.Reflection.Examples/Structurizr.Reflection.Examples.csproj @@ -7,7 +7,7 @@ false - + diff --git a/Structurizr.Reflection/Structurizr.Reflection.csproj b/Structurizr.Reflection/Structurizr.Reflection.csproj index 92fc045..2a5596f 100644 --- a/Structurizr.Reflection/Structurizr.Reflection.csproj +++ b/Structurizr.Reflection/Structurizr.Reflection.csproj @@ -20,11 +20,7 @@ - - - - - + diff --git a/Structurizr.sln b/Structurizr.sln index e62dc9f..ea60d98 100644 --- a/Structurizr.sln +++ b/Structurizr.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26430.15 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31410.357 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{4ED0E8DB-8A5B-4DF0-9CB1-995D97A04B88}" ProjectSection(SolutionItems) = preProject @@ -42,6 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{4FBA61 ProjectSection(SolutionItems) = preProject docs\images\c4-plantuml-getting-started.png = docs\images\c4-plantuml-getting-started.png docs\images\c4-plantuml-getting-started2.png = docs\images\c4-plantuml-getting-started2.png + docs\images\c4-plantuml-getting-started3.png = docs\images\c4-plantuml-getting-started3.png docs\images\plantuml-getting-started.png = docs\images\plantuml-getting-started.png docs\images\structurizr-annotations-1.png = docs\images\structurizr-annotations-1.png docs\images\structurizr-banner.png = docs\images\structurizr-banner.png diff --git a/docs/c4-plantuml.md b/docs/c4-plantuml.md index 0141c60..fa51905 100644 --- a/docs/c4-plantuml.md +++ b/docs/c4-plantuml.md @@ -1,16 +1,14 @@ # C4-PlantUML -Structurizr for .NET also includes a simple exporter that can create diagram definitions compatible with [C4-PlantUML](https://github.com/RicardoNiepel/C4-PlantUML). The following diagram types are supported: +Structurizr for .NET also includes a simple exporter that can create diagram definitions compatible with [C4-PlantUML v2.2.0](https://github.com/plantuml-stdlib/C4-PlantUML). +Following diagram types are supported: - Enterprise Context - System Context - Container - Component -- Dynamic* -- Deployment* - -*..Dynamic and Deployment diagrams are part of an open pull request (from https://github.com/kirchsth/C4-PlantUML). The diagrams can use the definitions via -CustomBaseUrl=https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/ or if it is not set then the definition is merged in each diagram) +- Dynamic +- Deployment Simply create your software architecture model and views as usual, and use the [C4PlantUMLWriter](../Structurizr.PlantUML/IO/C4PlantUML/C4PlantUMLWriter.cs) class to export the views. [For example](../Structurizr.Examples/C4PlantUML.cs): @@ -19,7 +17,7 @@ Workspace workspace = new Workspace("Getting Started", "This is a model of my so Model model = workspace.Model; model.Enterprise = new Enterprise("Some Enterprise"); - + Person user = model.AddPerson("User", "A user of my software system."); SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); var userUsesSystemRelation = user.Uses(softwareSystem, "Uses"); @@ -53,13 +51,13 @@ This code will generate and output a PlantUML diagram definition that looks like ' Structurizr.SystemContextView: SystemContext title Software System - System Context -LAYOUT_WITH_LEGEND() - Enterprise_Boundary(SomeEnterprise, "Some Enterprise") { System(SoftwareSystem__33c0d9d, "Software System", "My software system.") Person(User__378734a, "User", "A user of my software system.") Rel_Right(User__378734a, SoftwareSystem__33c0d9d, "Uses") } + +SHOW_LEGEND() @enduml ``` @@ -67,26 +65,39 @@ If you copy/paste this into [PlantUML online](http://www.plantuml.com/plantuml/) ![A simple C4-PlantUML diagram](images/c4-plantuml-getting-started.png) -__Mark containers or components as Database__ +__Mark containers or components as database or via tags__ Additional to the relation directions (via .SetDirection(), see above) is it possible to activate the database symbols in the diagrams via component and container specific *.IsDatabase(true) calls. +[C4-PlantUML v2.2.0](https://github.com/plantuml-stdlib/C4-PlantUML) supports tags (via `.Tags =`) an styles (`.Styles.Add()`) too. ```c# Container webApplication = softwareSystem.AddContainer("Web Application", "Delivers content", "Java and spring MVC"); +// Additional tag element +webApplication.Tags = "Single Page App"; + Container database = softwareSystem.AddContainer("Database", "Stores information", "Relational Database Schema"); // Additional mark it as database database.SetIsDatabase(true); -user.Uses(webApplication, "uses", "HTTP"); + +var httpCall = user.Uses(webApplication, "uses", "HTTP"); +// Additional tag relationship +httpCall.Tags = "via firewall"; + webApplication.Uses(database, "Reads from and writes to", "JDBC").SetDirection(DirectionValues.Right); +// add corresponding styles +var styles = views.Configuration.Styles; +styles.Add(new ElementStyle("Single Page App") {Background = "#5F9061", Stroke = "#2E4F2E", Color = "#FFFFFF" }); +styles.Add(new RelationshipStyle("via firewall") {Color = "#B40404", Dashed = true }); // dashed is supported with next version see below + var containerView = views.CreateContainerView(softwareSystem, "containers", ""); containerView.AddAllElements(); using (var stringWriter = new StringWriter()) { var plantUmlWriter = new C4PlantUmlWriter(); - plantUmlWriter.Write(containerView, stringWriter); + plantUmlWriter.Write(containerView, workspace.Views.Configuration, stringWriter); Console.WriteLine(stringWriter.ToString()); } ``` @@ -100,15 +111,18 @@ This code will generate and output a PlantUML diagram definition that looks like ' Structurizr.ContainerView: containers title Software System - Containers -LAYOUT_WITH_LEGEND() +AddElementTag(Single Page App, $bgColor = "#5f9061", $fontColor = "#ffffff", $borderColor = "#2e4f2e") +AddRelTag(via firewall, $textColor = "#b40404", $lineColor = "#b40404") Person(User__378734a, "User", "A user of my software system.") System_Boundary(SoftwareSystem__33c0d9d, "Software System") { ContainerDb(SoftwareSystem__Database__202c666, "Database", "Relational Database Schema", "Stores information") - Container(SoftwareSystem__WebApplication__2004eee, "Web Application", "Java and spring MVC", "Delivers content") + Container(SoftwareSystem__WebApplication__2004eee, "Web Application", "Java and spring MVC", "Delivers content", $tags="Single Page App") } -Rel(User__378734a, SoftwareSystem__WebApplication__2004eee, "uses", "HTTP") +Rel(User__378734a, SoftwareSystem__WebApplication__2004eee, "uses", "HTTP", $tags="via firewall") Rel_Right(SoftwareSystem__WebApplication__2004eee, SoftwareSystem__Database__202c666, "Reads from and writes to", "JDBC") + +SHOW_LEGEND() @enduml ``` @@ -116,6 +130,55 @@ You will get something like this: ![A simple C4-PlantUML diagram](images/c4-plantuml-getting-started2.png) +__Use features of the next planned C4-PlantUML version (v2.3.0 ?)__ + +The next version will support the correct Person shape and e.g. dotted lines. These features can be activated via `.EnableNextFeatures=true` +(until the next version is released please use `.CustomBaseUrl="https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/"`) + +```c# +using (var stringWriter = new StringWriter()) +{ + var plantUmlWriter = new C4PlantUmlWriter(); + plantUmlWriter.EnableNextFeatures = true; + plantUmlWriter.CustomBaseUrl = "https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/"; + + plantUmlWriter.Write(containerView, workspace.Views.Configuration, stringWriter); + Console.WriteLine(stringWriter.ToString()); +} +``` + +This code will generate and output a PlantUML diagram definition that looks like this: + +``` +@startuml +!includeurl https://raw.githubusercontent.com/kirchsth/C4-PlantUML/extended/C4_Container.puml + +' Structurizr.ContainerView: containers +title Software System - Containers + +SHOW_PERSON_OUTLINE() +AddRelTag("Back", $textColor=$ARROW_COLOR, $lineColor=$ARROW_COLOR, $lineStyle = DottedLine()) + +AddElementTag(Single Page App, $bgColor = "#5f9061", $fontColor = "#ffffff", $borderColor = "#2e4f2e", $shape = RoundedBoxShape()) +AddRelTag(via firewall, $textColor = "#b40404", $lineColor = "#b40404", $lineStyle = DashedLine()) + +Person(User__378734a, "User", "A user of my software system.") +System_Boundary(SoftwareSystem__33c0d9d, "Software System") { + ContainerDb(SoftwareSystem__Database__202c666, "Database", "Relational Database Schema", "Stores information") + Container(SoftwareSystem__WebApplication__2004eee, "Web Application", "Java and spring MVC", "Delivers content", $tags="Single Page App") +} +Rel(User__378734a, SoftwareSystem__WebApplication__2004eee, "uses", "HTTP", $tags="via firewall") +Rel_Right(SoftwareSystem__WebApplication__2004eee, SoftwareSystem__Database__202c666, "Reads from and writes to", "JDBC") + +SHOW_LEGEND() +@enduml +``` + +You will get something like this: + +![A simple C4-PlantUML diagram](images/c4-plantuml-getting-started3.png) + + ## Benefits of using C4-PlantUML with Structurizr The key benefit of using PlantUML in conjunction with the Structurizr client library is that you can create diagrams from a __model__ of your software system. The model provides a set of rules that must be followed; related to elements, relationships, and how they are exposed using diagrams. This means: diff --git a/docs/images/c4-plantuml-getting-started.png b/docs/images/c4-plantuml-getting-started.png index cd16e37..0e9afdd 100644 Binary files a/docs/images/c4-plantuml-getting-started.png and b/docs/images/c4-plantuml-getting-started.png differ diff --git a/docs/images/c4-plantuml-getting-started2.png b/docs/images/c4-plantuml-getting-started2.png index b3b63d4..a931c99 100644 Binary files a/docs/images/c4-plantuml-getting-started2.png and b/docs/images/c4-plantuml-getting-started2.png differ diff --git a/docs/images/c4-plantuml-getting-started3.png b/docs/images/c4-plantuml-getting-started3.png new file mode 100644 index 0000000..bfbaaf0 Binary files /dev/null and b/docs/images/c4-plantuml-getting-started3.png differ