├── .travis.yml ├── docs ├── images │ ├── structurizr-banner.png │ ├── structurizr-overview.png │ ├── plantuml-getting-started.png │ ├── structurizr-annotations-1.png │ ├── c4-plantuml-getting-started.png │ └── c4-plantuml-getting-started2.png ├── binaries.md ├── changelog.md ├── plantuml.md ├── c4-plantuml.md └── structurizr-annotations.md ├── Structurizr.Examples ├── structurizr-logo.png ├── Documentation │ └── adr │ │ ├── 0001-record-architecture-decisions.md │ │ ├── 0002-implement-as-shell-scripts.md │ │ ├── 0004-markdown-format.md │ │ ├── 0005-help-comments.md │ │ ├── 0007-invoke-adr-config-executable-to-get-configuration.md │ │ ├── 0003-single-command-with-subcommands.md │ │ ├── 0006-packaging-and-distribution-in-other-version-control-repositories.md │ │ └── 0008-use-iso-8601-format-for-dates.md ├── PlantUML.cs ├── Structurizr.Examples.csproj ├── C4PlantUML.cs └── AdrTools.cs ├── Structurizr.AdrTools.Tests ├── TestFailedException.cs ├── AdrTools │ ├── adrs │ │ ├── 0001-record-architecture-decisions.md │ │ ├── 0010-asciidoc-format.md │ │ ├── 0002-implement-as-shell-scripts.md │ │ ├── 0009-help-scripts.md │ │ ├── 0004-markdown-format.md │ │ ├── 0005-help-comments.md │ │ ├── 0007-invoke-adr-config-executable-to-get-configuration.md │ │ ├── 0003-single-command-with-subcommands.md │ │ ├── 0006-packaging-and-distribution-in-other-version-control-repositories.md │ │ └── 0008-use-iso-8601-format-for-dates.md │ └── AdrToolsImporterTests.cs └── Structurizr.AdrTools.Tests.csproj ├── Structurizr.PlantUML ├── IO │ └── C4PlantUML │ │ ├── ModelExtensions │ │ ├── BooleanValues.cs │ │ ├── Properties.cs │ │ ├── ComponentExtensions.cs │ │ ├── ContainerExtensions.cs │ │ ├── DirectionValues.cs │ │ ├── RelationshipExtensions.cs │ │ └── RelationshipViewExtensions.cs │ │ └── IPlantUMLWriter.cs └── Structurizr.PlantUML.csproj ├── Structurizr.Cecil.Examples ├── Annotations │ ├── IRepository.cs │ ├── HtmlController.cs │ └── EfRepository.cs ├── Structurizr.Cecil.Examples.csproj └── StructurizrAnnotations.cs ├── Structurizr.Reflection.Examples ├── Annotations │ ├── IRepository.cs │ ├── HtmlController.cs │ └── EfRepository.cs ├── Structurizr.Reflection.Examples.csproj └── StructurizrAnnotations.cs ├── README.md ├── Structurizr.Analysis ├── Structurizr.Analysis.csproj └── Analysis │ ├── ComponentFinderStrategy.cs │ └── ComponentFinder.cs ├── Structurizr.AdrTools ├── Structurizr.AdrTools.csproj └── AdrTools │ └── AdrToolsImporter.cs ├── Structurizr.ActiveDirectory ├── Structurizr.ActiveDirectory.csproj ├── AzureActiveDirectoryStructurizrClient.cs └── Program.cs ├── Structurizr.PlantUML.Tests ├── Structurizr.PlantUML.Tests.csproj └── Utilities │ └── UnifyExtension.cs ├── appveyor.yml ├── Structurizr.Annotations ├── ComponentAttribute.cs ├── CodeElementAttribute.cs ├── UsedByPersonAttribute.cs ├── UsedByContainerAttribute.cs ├── UsesContainerAttribute.cs ├── UsedBySoftwareSystemAttribute.cs ├── UsesSoftwareSystemAttribute.cs ├── UsesComponentAttribute.cs └── Structurizr.Annotations.csproj ├── Structurizr.Reflection ├── Analysis │ ├── SupportingTypes │ │ ├── SupportingTypesStrategy.cs │ │ └── ReferencedTypesSupportingTypesStrategy.cs │ ├── ITypeMatcher.cs │ ├── ITypeRepository.cs │ ├── NameSuffixTypeMatcher.cs │ ├── ExtendsClassMatcher.cs │ ├── InterfaceImplementationTypeMatcher.cs │ └── TypeMatcherComponentFinderStrategy.cs └── Structurizr.Reflection.csproj ├── Structurizr.Cecil ├── Analysis │ ├── SupportingTypes │ │ ├── SupportingTypesStrategy.cs │ │ └── ReferencedTypesSupportingTypesStrategy.cs │ ├── ITypeMatcher.cs │ ├── ITypeRepository.cs │ ├── NameSuffixTypeMatcher.cs │ ├── ExtendsClassTypeMatcher.cs │ ├── InterfaceImplementationTypeMatcher.cs │ ├── CustomAttributeTypeMatcher.cs │ └── TypeMatcherComponentFinderStrategy.cs ├── Structurizr.Cecil.csproj └── Util │ ├── TypeReferenceExtensions.cs │ ├── AssemblyDefinitionExtensions.cs │ ├── TypeDefinitionExtensions.cs │ └── CustomAttributeExtensions.cs ├── Structurizr.Roslyn ├── app.config ├── Structurizr.Roslyn.csproj └── Analysis │ └── TypeSummaryComponentFinderStrategy.cs ├── .gitattributes └── .gitignore /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | solution: Structurizr.sln 3 | -------------------------------------------------------------------------------- /docs/images/structurizr-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/dotnet-extensions/HEAD/docs/images/structurizr-banner.png -------------------------------------------------------------------------------- /docs/images/structurizr-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/dotnet-extensions/HEAD/docs/images/structurizr-overview.png -------------------------------------------------------------------------------- /Structurizr.Examples/structurizr-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/dotnet-extensions/HEAD/Structurizr.Examples/structurizr-logo.png -------------------------------------------------------------------------------- /docs/images/plantuml-getting-started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/dotnet-extensions/HEAD/docs/images/plantuml-getting-started.png -------------------------------------------------------------------------------- /docs/images/structurizr-annotations-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/dotnet-extensions/HEAD/docs/images/structurizr-annotations-1.png -------------------------------------------------------------------------------- /docs/images/c4-plantuml-getting-started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/dotnet-extensions/HEAD/docs/images/c4-plantuml-getting-started.png -------------------------------------------------------------------------------- /docs/images/c4-plantuml-getting-started2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/structurizr/dotnet-extensions/HEAD/docs/images/c4-plantuml-getting-started2.png -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/TestFailedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.AdrTools.Tests 4 | { 5 | public class TestFailedException : Exception 6 | { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/BooleanValues.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizr.IO.C4PlantUML.ModelExtensions 2 | { 3 | public class BooleanValues 4 | { 5 | public const string True = "true"; 6 | public const string False = "false"; 7 | } 8 | } -------------------------------------------------------------------------------- /Structurizr.Cecil.Examples/Annotations/IRepository.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Annotations; 2 | 3 | namespace Structurizr.Examples.Annotations 4 | { 5 | [Component(Description = "Provides access to data stored in the database.", Technology = "C#")] 6 | public interface IRepository 7 | { 8 | string GetData(long id); 9 | } 10 | } -------------------------------------------------------------------------------- /Structurizr.Reflection.Examples/Annotations/IRepository.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Annotations; 2 | 3 | namespace Structurizr.Examples.Annotations 4 | { 5 | [Component(Description = "Provides access to data stored in the database.", Technology = "C#")] 6 | public interface IRepository 7 | { 8 | string GetData(long id); 9 | } 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Structurizr for .NET extensions 2 | 3 | The code in this repo is unsupported and has been archived. The [Structurizr DSL](https://github.com/structurizr/dsl) is the recommended tooling for authoring Structurizr workspaces, while the [Structurizr CLI](https://github.com/structurizr/cli) can be used to push/pull workspaces and export views to PlantUML, Mermaid, etc. -------------------------------------------------------------------------------- /Structurizr.Cecil.Examples/Annotations/HtmlController.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Annotations; 2 | 3 | namespace Structurizr.Examples.Annotations 4 | { 5 | [Component(Description = "Serves HTML pages to users.", Technology = "ASP.NET MVC")] 6 | [UsedByPerson("User", Description = "Uses", Technology = "HTTPS")] 7 | class HtmlController 8 | { 9 | [UsesComponent("Gets data using")] 10 | private IRepository repository = new EfRepository(); 11 | } 12 | } -------------------------------------------------------------------------------- /Structurizr.Cecil.Examples/Annotations/EfRepository.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Annotations; 2 | 3 | namespace Structurizr.Examples.Annotations 4 | { 5 | [CodeElement(nameof(IRepository), Description = "Implementation")] 6 | [UsesContainer("Database", Description = "Reads from", Technology = "Entity Framework")] 7 | class EfRepository : IRepository 8 | { 9 | public string GetData(long id) 10 | { 11 | return "..."; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Structurizr.Reflection.Examples/Annotations/HtmlController.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Annotations; 2 | 3 | namespace Structurizr.Examples.Annotations 4 | { 5 | [Component(Description = "Serves HTML pages to users.", Technology = "ASP.NET MVC")] 6 | [UsedByPerson("User", Description = "Uses", Technology = "HTTPS")] 7 | class HtmlController 8 | { 9 | [UsesComponent("Gets data using")] 10 | private IRepository repository = new EfRepository(); 11 | } 12 | } -------------------------------------------------------------------------------- /Structurizr.Reflection.Examples/Annotations/EfRepository.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Annotations; 2 | 3 | namespace Structurizr.Examples.Annotations 4 | { 5 | [CodeElement(nameof(IRepository), Description = "Implementation")] 6 | [UsesContainer("Database", Description = "Reads from", Technology = "Entity Framework")] 7 | class EfRepository : IRepository 8 | { 9 | public string GetData(long id) 10 | { 11 | return "..."; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/IPlantUMLWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | // Source base version copied from https://gist.github.com/coldacid/465fa8f3a4cd3fdd7b640a65ad5b86f4 (https://github.com/structurizr/dotnet/issues/47) 4 | // kirchsth: Extended with dynamic and deployment view 5 | namespace Structurizr.IO.C4PlantUML 6 | { 7 | public interface IPlantUMLWriter 8 | { 9 | void Write(Workspace workspace, TextWriter writer); 10 | void Write(View view, TextWriter writer); 11 | } 12 | } -------------------------------------------------------------------------------- /Structurizr.Analysis/Structurizr.Analysis.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | netstandard1.3;net45 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Structurizr.AdrTools/Structurizr.AdrTools.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | netstandard1.3;net45 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0001-record-architecture-decisions.md: -------------------------------------------------------------------------------- 1 | # 1. Record architecture decisions 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | We need to record the architectural decisions made on this project. 12 | 13 | ## Decision 14 | 15 | We will use Architecture Decision Records, as described by Michael Nygard in this article: http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions 16 | 17 | ## Consequences 18 | 19 | See Michael Nygard's article, linked above. 20 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0010-asciidoc-format.md: -------------------------------------------------------------------------------- 1 | # 10. AsciiDoc format 2 | 3 | Date: 2018-08-11 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | Supercedes [4. Markdown format](0004-markdown-format.md) 10 | 11 | ## Context 12 | 13 | The issue motivating this decision, and any context that influences or constrains the decision. 14 | 15 | ## Decision 16 | 17 | The change that we're proposing or have agreed to implement. 18 | 19 | ## Consequences 20 | 21 | What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated. 22 | -------------------------------------------------------------------------------- /Structurizr.ActiveDirectory/Structurizr.ActiveDirectory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | Exe 11 | netcoreapp1.1 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Structurizr.PlantUML.Tests/Structurizr.PlantUML.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp1.1 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Structurizr.Cecil.Examples/Structurizr.Cecil.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net45 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0002-implement-as-shell-scripts.md: -------------------------------------------------------------------------------- 1 | # 2. Implement as shell scripts 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | ADRs are plain text files stored in a subdirectory of the project. 12 | 13 | The tool needs to create new files and apply small edits to 14 | the Status section of existing files. 15 | 16 | ## Decision 17 | 18 | The tool is implemented as shell scripts that use standard Unix 19 | tools -- grep, sed, awk, etc. 20 | 21 | ## Consequences 22 | 23 | The tool won't support Windows. Being plain text files, ADRs can 24 | be created by hand and edited in any text editor. This tool just 25 | makes the process more convenient. 26 | 27 | Development will have to cope with differences between Unix 28 | variants, particularly Linux and MacOS X. 29 | -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/Properties.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Source base version copied from https://gist.github.com/coldacid/465fa8f3a4cd3fdd7b640a65ad5b86f4 (https://github.com/structurizr/dotnet/issues/47) 4 | // kirchsth: Extended with dynamic and deployment view 5 | namespace Structurizr.IO.C4PlantUML.ModelExtensions 6 | { 7 | public class Properties 8 | { 9 | /// 10 | /// This property enables to mark Components and Container as database with value "true" 11 | /// 12 | public const string IsDatabase = "IsDatabase"; 13 | 14 | /// 15 | /// This property enables to define a specific (default) direction of Relationships or RelationshipViews 16 | /// 17 | public const string Direction = "Direction"; 18 | } 19 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | image: Visual Studio 2017 5 | build_script: 6 | - ps: >- 7 | dotnet restore 8 | 9 | dotnet build 10 | 11 | 12 | dotnet clean /p:Configuration=Release 13 | 14 | if ($env:APPVEYOR_REPO_TAG -Eq "true") { 15 | dotnet msbuild "/t:Restore;Pack" /p:Configuration=Release /p:Version=$env:APPVEYOR_REPO_TAG_NAME 16 | } else { 17 | dotnet msbuild "/t:Restore;Pack" /p:Configuration=Release /p:VersionSuffix=pre${env:APPVEYOR_BUILD_NUMBER}-$($env:APPVEYOR_REPO_COMMIT.Substring(0,8)) 18 | } 19 | test_script: 20 | - ps: dotnet test .\Structurizr.AdrTools.Tests\Structurizr.AdrTools.Tests.csproj 21 | - ps: dotnet test .\Structurizr.PlantUML.Tests\Structurizr.PlantUML.Tests.csproj 22 | artifacts: 23 | - path: '*\bin\Release\*.nupkg' -------------------------------------------------------------------------------- /Structurizr.Reflection.Examples/Structurizr.Reflection.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net452 6 | Structurizr.Examples.ContosoUniversity 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0009-help-scripts.md: -------------------------------------------------------------------------------- 1 | # 9. Help scripts 2 | 3 | Date: 2018-06-26 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | Amends [5. Help comments](0005-help-comments.md) 10 | 11 | ## Context 12 | 13 | Currently help text is generated by extracting specially formatted comments from the top of the command script. 14 | 15 | This makes it easy for developers of the tool: documentation and code is all in one place. 16 | 17 | But, it means that help text cannot include calculated values, such as the location of files. 18 | 19 | ## Decision 20 | 21 | Where necessary, help text can be generated by a script. 22 | 23 | The script will be called _adr_help__ 24 | 25 | ## Consequences 26 | 27 | Help scripts can include helper scripts to locate files, giving more accurate instructions to the user that reflect how the tool is deployed in their environment. 28 | 29 | -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0001-record-architecture-decisions.md: -------------------------------------------------------------------------------- 1 | # 1. Record architecture decisions 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | We need to record the architectural decisions made on this project. 12 | 13 | ## Decision 14 | 15 | We will use Architecture Decision Records, as described by Michael Nygard in this article: [http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) 16 | 17 | ## Consequences 18 | 19 | See Michael Nygard's article, linked above. 20 | 21 | --- 22 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/ComponentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.IO.C4PlantUML.ModelExtensions 4 | { 5 | public static class ComponentExtensions 6 | { 7 | public static bool GetIsDatabase(this Component container) 8 | { 9 | string value = string.Empty; 10 | if (container.Properties?.TryGetValue(Properties.IsDatabase, out value) == true) 11 | return string.Compare(BooleanValues.True, value, StringComparison.OrdinalIgnoreCase) == 0; 12 | 13 | return false; 14 | } 15 | 16 | public static void SetIsDatabase(this Component container, bool isDatabase) 17 | { 18 | if (isDatabase) 19 | container.Properties[Properties.IsDatabase] = BooleanValues.True; 20 | else 21 | container.Properties.Remove(Properties.IsDatabase); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/ContainerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.IO.C4PlantUML.ModelExtensions 4 | { 5 | public static class ContainerExtensions 6 | { 7 | public static bool GetIsDatabase(this Container container) 8 | { 9 | string value = string.Empty; 10 | if (container.Properties?.TryGetValue(Properties.IsDatabase, out value) == true) 11 | return string.Compare(BooleanValues.True, value, StringComparison.OrdinalIgnoreCase) == 0; 12 | 13 | return false; 14 | } 15 | 16 | public static void SetIsDatabase(this Container container, bool isDatabase) 17 | { 18 | if (isDatabase) 19 | container.Properties[Properties.IsDatabase] = BooleanValues.True; 20 | else 21 | container.Properties.Remove(Properties.IsDatabase); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Structurizr.PlantUML/Structurizr.PlantUML.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | Provides the ability to export view definitions to PlantUML diagram definitions. 11 | Structurizr Limited 12 | Copyright 2018 13 | https://opensource.org/licenses/Apache-2.0 14 | https://structurizr.com 15 | https://github.com/structurizr/dotnet 16 | 17 | 18 | 19 | netstandard1.3;net45 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/binaries.md: -------------------------------------------------------------------------------- 1 | # Binaries 2 | 3 | The "Structurizr for .NET" binaries are hosted on [NuGet](https://www.nuget.org/profiles/structurizr) as follows: 4 | 5 | Name | Description 6 | --------------------- | --------------------------------------------------------------------------------------------------------------------------- 7 | Structurizr.Analysis | Provides analysis capabilities, using reflection on compiled bytecode to find components. 8 | Structurizr.AdrTools | Imports architecture decision records (ADRs) from the adr-tools tooling. 9 | Structurizr.Annotations | Annotation attribute library for descriptive architecture in code. 10 | Structurizr.Cecil | Architecture analysis library built around the Mono.Cecil library. 11 | Structurizr.PlantUML | Provides the ability to export view definitions to PlantUML diagram definitions. 12 | Structurizr.Reflection | Architecture analysis library built around .NET reflection. 13 | Structurizr.Roslyn | Architecture analysis library built around the Roslyn compiler. 14 | -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/DirectionValues.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizr.IO.C4PlantUML.ModelExtensions 2 | { 3 | public sealed class DirectionValues 4 | { 5 | public const string NotSet = ""; 6 | 7 | public const string Back = "Back"; 8 | /// 9 | /// C4 PlantUml writes without u 10 | /// 11 | public const string Neighbor = "Neighbor"; 12 | /// 13 | /// C4 PlantUml writes without u 14 | /// 15 | public const string Neighbour = "Neighbour"; 16 | /// 17 | /// C4 PlantUml writes without u 18 | /// 19 | public const string BackNeighbor = "BackNeighbor"; 20 | /// 21 | /// C4 PlantUml writes without u 22 | /// 23 | public const string BackNeighbour = "BackNeighbour"; 24 | 25 | public const string Up = "Up"; 26 | public const string Down = "Down"; 27 | public const string Left = "Left"; 28 | public const string Right = "Right"; 29 | } 30 | } -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0002-implement-as-shell-scripts.md: -------------------------------------------------------------------------------- 1 | # 2. Implement as shell scripts 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | ADRs are plain text files stored in a subdirectory of the project. 12 | 13 | The tool needs to create new files and apply small edits to 14 | the Status section of existing files. 15 | 16 | ## Decision 17 | 18 | The tool is implemented as shell scripts that use standard Unix 19 | tools -- grep, sed, awk, etc. 20 | 21 | ## Consequences 22 | 23 | The tool won't support Windows. Being plain text files, ADRs can 24 | be created by hand and edited in any text editor. This tool just 25 | makes the process more convenient. 26 | 27 | Development will have to cope with differences between Unix 28 | variants, particularly Linux and MacOS X. 29 | 30 | --- 31 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /Structurizr.Annotations/ComponentAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the attributed type class or interface can be considered to be a "component". 7 | /// 8 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] 9 | public sealed class ComponentAttribute : Attribute 10 | { 11 | /// 12 | /// Gets or sets a description of the component. 13 | /// 14 | /// A description of the component. 15 | public string Description { get; set; } = String.Empty; 16 | 17 | /// 18 | /// Gets or sets a description of the technology used to implement the component. 19 | /// 20 | /// A description of the technology used to implement the component. 21 | public string Technology { get; set; } = String.Empty; 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | public ComponentAttribute() {} 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/RelationshipExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizr.IO.C4PlantUML.ModelExtensions 2 | { 3 | public static class RelationshipExtensions 4 | { 5 | public static string GetDirection(this Relationship relationship) 6 | { 7 | string value = DirectionValues.NotSet; 8 | if (relationship.Properties?.TryGetValue(Properties.Direction, out value) == true) 9 | return value; 10 | 11 | return value; 12 | } 13 | 14 | /// 15 | /// Set a (default) direction of the relation which should be used in all C4PlantUML views 16 | /// 17 | /// 18 | /// one of 19 | public static void SetDirection(this Relationship relationship, string direction) 20 | { 21 | if (string.IsNullOrWhiteSpace(direction)) // direction DirectionValues.NotSet 22 | relationship.Properties.Remove(Properties.Direction); 23 | else 24 | relationship.Properties[Properties.Direction] = direction; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0004-markdown-format.md: -------------------------------------------------------------------------------- 1 | # 4. Markdown format 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Superceded by [10. AsciiDoc format](0010-asciidoc-format.md) 8 | 9 | ## Context 10 | 11 | The decision records must be stored in a plain text format: 12 | 13 | * This works well with version control systems. 14 | 15 | * It allows the tool to modify the status of records and insert 16 | hyperlinks when one decision supercedes another. 17 | 18 | * Decisions can be read in the terminal, IDE, version control 19 | browser, etc. 20 | 21 | People will want to use some formatting: lists, code examples, 22 | and so on. 23 | 24 | People will want to view the decision records in a more readable 25 | format than plain text, and maybe print them out. 26 | 27 | 28 | ## Decision 29 | 30 | Record architecture decisions in [Markdown format](https://daringfireball.net/projects/markdown/). 31 | 32 | ## Consequences 33 | 34 | Decisions can be read in the terminal. 35 | 36 | Decisions will be formatted nicely and hyperlinked by the 37 | browsers of project hosting sites like GitHub and Bitbucket. 38 | 39 | Tools like [Pandoc](http://pandoc.org/) can be used to convert 40 | the decision records into HTML or PDF. 41 | -------------------------------------------------------------------------------- /Structurizr.Analysis/Analysis/ComponentFinderStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | 6 | /// 7 | /// The interface that all component finder strategies must implement. 8 | /// 9 | public interface ComponentFinderStrategy 10 | { 11 | 12 | /// 13 | /// A reference to the parent ComponentFinder. 14 | /// 15 | ComponentFinder ComponentFinder {set; } 16 | 17 | /// 18 | /// Called before all component finder strategies belonging to the 19 | /// same component finder are asked to find components. 20 | /// 21 | void BeforeFindComponents(); 22 | 23 | /// 24 | /// Finds components. 25 | /// 26 | IEnumerable FindComponents(); 27 | 28 | /// 29 | /// Called after all component finder strategies belonging to the 30 | /// same component finder have found components. This can be used 31 | /// to supplement the component with more information, such as 32 | /// dependencies. 33 | /// 34 | void AfterFindComponents(); 35 | 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/SupportingTypes/SupportingTypesStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Provides a base type for strategies used to identify supporting types for components. 7 | /// 8 | /// 9 | /// 10 | public abstract class SupportingTypesStrategy 11 | { 12 | 13 | /// 14 | /// Gets the type repository to use for looking up supporting types. 15 | /// 16 | /// The type repository instance provided by the component finder strategy using this strategy. 17 | public ITypeRepository TypeRepository { get; set; } 18 | 19 | /// 20 | /// Identifies and returns supporting types of the provided . 21 | /// 22 | /// The component for which supporting types are to be found. 23 | /// A set of assembly qualified type names for types which support the component. 24 | public abstract HashSet FindSupportingTypes(Component component); 25 | 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/SupportingTypes/SupportingTypesStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Provides a base type for strategies used to identify supporting types for components. 7 | /// 8 | /// 9 | /// 10 | public abstract class SupportingTypesStrategy 11 | { 12 | 13 | /// 14 | /// Gets the type repository to use for looking up supporting types. 15 | /// 16 | /// The type repository instance provided by the component finder strategy using this strategy. 17 | protected internal ITypeRepository TypeRepository { get; internal set; } 18 | 19 | /// 20 | /// Identifies and returns supporting types of the provided . 21 | /// 22 | /// The component for which supporting types are to be found. 23 | /// A set of assembly qualified type names for types which support the component. 24 | public abstract HashSet FindSupportingTypes(Component component); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Structurizr.Cecil/Structurizr.Cecil.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | Mono.Cecil based software architecture analysis for Structurizr.Core 11 | Structurizr Limited 12 | Copyright 2017 13 | https://opensource.org/licenses/Apache-2.0 14 | https://structurizr.com 15 | https://github.com/structurizr/dotnet 16 | 17 | 18 | 19 | netstandard1.3;net45 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0005-help-comments.md: -------------------------------------------------------------------------------- 1 | # 5. Help comments 2 | 3 | Date: 2016-02-13 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | Amended by [9. Help scripts](0009-help-scripts.md) 10 | 11 | ## Context 12 | 13 | The tool will have a `help` subcommand to provide documentation 14 | for users. 15 | 16 | It's nice to have usage documentation in the script files 17 | themselves, in comments. When reading the code, that's the first 18 | place to look for information about how to run a script. 19 | 20 | ## Decision 21 | 22 | Write usage documentation in comments in the source file. 23 | 24 | Distinguish between documentation comments and normal comments. 25 | Documentation comments have two hash characters at the start of 26 | the line. 27 | 28 | The `adr help` command can parse comments out from the script 29 | using the standard Unix tools `grep` and `cut`. 30 | 31 | ## Consequences 32 | 33 | No need to maintain help text in a separate file. 34 | 35 | Help text can easily be kept up to date as the script is edited. 36 | 37 | There's no automated check that the help text is up to date. The 38 | tests do not work well as documentation for users, and the help 39 | text is not easily cross-checked against the code. 40 | 41 | This won't work if any subcommands are not implemented as scripts 42 | that use '#' as a comment character. 43 | -------------------------------------------------------------------------------- /Structurizr.Reflection/Structurizr.Reflection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | .NET Reflection based software architecture analysis for Structurizr.Core 11 | Structurizr Limited 12 | Copyright 2017 13 | https://opensource.org/licenses/Apache-2.0 14 | https://structurizr.com 15 | https://github.com/structurizr/dotnet 16 | 17 | 18 | 19 | netstandard2.0;net452 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0007-invoke-adr-config-executable-to-get-configuration.md: -------------------------------------------------------------------------------- 1 | # 7. Invoke adr-config executable to get configuration 2 | 3 | Date: 2016-12-17 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | Packagers (e.g. Homebrew developers) want to configure adr-tools to match the conventions of their installation. 12 | 13 | Currently, this is done by sourcing a file `config.sh`, which should sit beside the `adr` executable. 14 | 15 | This name is too common. 16 | 17 | The `config.sh` file is not executable, and so doesn't belong in a bin directory. 18 | 19 | ## Decision 20 | 21 | Replace `config.sh` with an executable, named `adr-config` that outputs configuration. 22 | 23 | Each script in ADR Tools will eval the output of `adr-config` to configure itself. 24 | 25 | ## Consequences 26 | 27 | Configuration within ADR Tools is a little more complicated. 28 | 29 | Packagers can write their own implementation of `adr-config` that outputs configuration that matches the platform's installation conventions, and deploy it next to the `adr` script. 30 | 31 | To make development easier, the implementation of `adr-config` in the project's src/ directory will output configuration that lets the tool to run from the src/ directory without any installation step. (Packagers should not include this script in a deployable package.) 32 | -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/ITypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Declares an interface for component identification rules. 7 | /// 8 | public interface ITypeMatcher 9 | { 10 | 11 | /// 12 | /// Checks the given matches against the component identification rule. 13 | /// 14 | /// The of the type to match. 15 | /// if the type matches against the rule; otherwise, . 16 | bool Matches(Type type); 17 | 18 | /// 19 | /// Gets the description to use for components matching the rule. 20 | /// 21 | /// A string containing the Description property for matching components. 22 | /// 23 | string GetDescription(); 24 | 25 | /// 26 | /// Gets the technology name(s) to use for components matching the rule. 27 | /// 28 | /// A string containing the Technology property for matching components. 29 | /// 30 | string GetTechnology(); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/ITypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Declares an interface for component identification rules. 7 | /// 8 | public interface ITypeMatcher 9 | { 10 | /// 11 | /// Checks the given matches against the component identification rule. 12 | /// 13 | /// The of the type to match. 14 | /// if the type matches against the rule; otherwise, . 15 | bool Matches(TypeDefinition type); 16 | 17 | /// 18 | /// Gets the description to use for components matching the rule. 19 | /// 20 | /// A string containing the Description property for matching components. 21 | /// 22 | string GetDescription(); 23 | 24 | /// 25 | /// Gets the technology name(s) to use for components matching the rule. 26 | /// 27 | /// A string containing the Technology property for matching components. 28 | /// 29 | string GetTechnology(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0003-single-command-with-subcommands.md: -------------------------------------------------------------------------------- 1 | # 3. Single command with subcommands 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | The tool provides a number of related commands to create 12 | and manipulate architecture decision records. 13 | 14 | How can the user find out about the commands that are available? 15 | 16 | ## Decision 17 | 18 | The tool defines a single command, called `adr`. 19 | 20 | The first argument to `adr` (the subcommand) specifies the 21 | action to perform. Further arguments are interpreted by the 22 | subcommand. 23 | 24 | Running `adr` without any arguments lists the available 25 | subcommands. 26 | 27 | Subcommands are implemented as scripts in the same 28 | directory as the `adr` script. E.g. the subcommand `new` is 29 | implemented as the script `adr-new`, the subcommand `help` 30 | as the script `adr-help` and so on. 31 | 32 | Helper scripts that are part of the implementation but not 33 | subcommands follow a different naming convention, so that 34 | subcommands can be listed by filtering and transforming script 35 | file names. 36 | 37 | ## Consequences 38 | 39 | Users can more easily explore the capabilities of the tool. 40 | 41 | Users are already used to this style of command-line tool. For 42 | example, Git works this way. 43 | 44 | Each subcommand can be implemented in the most appropriate 45 | language. 46 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0006-packaging-and-distribution-in-other-version-control-repositories.md: -------------------------------------------------------------------------------- 1 | # 6. Packaging and distribution in other version control repositories 2 | 3 | Date: 2016-02-16 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | Users want to install adr-tools with their preferred package 12 | manager. For example, Ubuntu users use `apt`, RedHat users use 13 | `yum` and Mac OS X users use [Homebrew](http://brew.sh). 14 | 15 | The developers of `adr-tools` don't know how, nor have permissions, 16 | to use all these packaging and distribution systems. Therefore packaging 17 | and distribution must be done by "downstream" parties. 18 | 19 | The developers of the tool should not favour any one particular 20 | packaging and distribution solution. 21 | 22 | ## Decision 23 | 24 | The `adr-tools` project will not contain any packaging or 25 | distribution scripts and config. 26 | 27 | Packaging and distribution will be managed by other projects in 28 | separate version control repositories. 29 | 30 | ## Consequences 31 | 32 | The git repo of this project will be simpler. 33 | 34 | Eventually, users will not have to use Git to get the software. 35 | 36 | We will have to tag releases in the `adr-tools` repository so that 37 | packaging projects know what can be published and how it should be 38 | identified. 39 | 40 | We will document how users can install the software in this 41 | project's README file. 42 | -------------------------------------------------------------------------------- /Structurizr.Examples/PlantUML.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Structurizr.IO.PlantUML; 4 | 5 | namespace Structurizr.Examples 6 | { 7 | 8 | /// 9 | /// An example of how to use the PlantUML writer. Run this program and copy/paste 10 | /// the output into http://www.plantuml.com/plantuml/ 11 | /// 12 | public class PlantUML 13 | { 14 | 15 | static void Main() 16 | { 17 | Workspace workspace = new Workspace("Getting Started", "This is a model of my software system."); 18 | Model model = workspace.Model; 19 | 20 | Person user = model.AddPerson("User", "A user of my software system."); 21 | SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); 22 | user.Uses(softwareSystem, "Uses"); 23 | 24 | ViewSet views = workspace.Views; 25 | SystemContextView contextView = views.CreateSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); 26 | contextView.AddAllSoftwareSystems(); 27 | contextView.AddAllPeople(); 28 | 29 | StringWriter stringWriter = new StringWriter(); 30 | PlantUMLWriter plantUMLWriter = new PlantUMLWriter(); 31 | plantUMLWriter.Write(workspace, stringWriter); 32 | Console.WriteLine(stringWriter.ToString()); 33 | } 34 | 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0004-markdown-format.md: -------------------------------------------------------------------------------- 1 | # 4. Markdown format 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | The decision records must be stored in a plain text format: 12 | 13 | * This works well with version control systems. 14 | 15 | * It allows the tool to modify the status of records and insert 16 | hyperlinks when one decision supercedes another. 17 | 18 | * Decisions can be read in the terminal, IDE, version control 19 | browser, etc. 20 | 21 | People will want to use some formatting: lists, code examples, 22 | and so on. 23 | 24 | People will want to view the decision records in a more readable 25 | format than plain text, and maybe print them out. 26 | 27 | 28 | ## Decision 29 | 30 | Record architecture decisions in [Markdown format](https://daringfireball.net/projects/markdown/). 31 | 32 | ## Consequences 33 | 34 | Decisions can be read in the terminal. 35 | 36 | Decisions will be formatted nicely and hyperlinked by the 37 | browsers of project hosting sites like GitHub and Bitbucket. 38 | 39 | Tools like [Pandoc](http://pandoc.org/) can be used to convert 40 | the decision records into HTML or PDF. 41 | 42 | --- 43 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0005-help-comments.md: -------------------------------------------------------------------------------- 1 | # 5. Help comments 2 | 3 | Date: 2016-02-13 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | The tool will have a `help` subcommand to provide documentation 12 | for users. 13 | 14 | It's nice to have usage documentation in the script files 15 | themselves, in comments. When reading the code, that's the first 16 | place to look for information about how to run a script. 17 | 18 | ## Decision 19 | 20 | Write usage documentation in comments in the source file. 21 | 22 | Distinguish between documentation comments and normal comments. 23 | Documentation comments have two hash characters at the start of 24 | the line. 25 | 26 | The `adr help` command can parse comments out from the script 27 | using the standard Unix tools `grep` and `cut`. 28 | 29 | ## Consequences 30 | 31 | No need to maintain help text in a separate file. 32 | 33 | Help text can easily be kept up to date as the script is edited. 34 | 35 | There's no automated check that the help text is up to date. The 36 | tests do not work well as documentation for users, and the help 37 | text is not easily cross-checked against the code. 38 | 39 | This won't work if any subcommands are not implemented as scripts 40 | that use '#' as a comment character. 41 | 42 | --- 43 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /Structurizr.Annotations/CodeElementAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the attributed type class or interface can be considered to be a "component". 7 | /// 8 | [AttributeUsage( 9 | AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, 10 | Inherited = false, 11 | AllowMultiple = false)] 12 | public sealed class CodeElementAttribute : Attribute 13 | { 14 | /// 15 | /// Gets the name of the component to which the attributed type belongs. 16 | /// 17 | /// The name of the component which includes the attributed type. 18 | public string ComponentName { get; } 19 | 20 | /// 21 | /// Gets or sets a description of the code element. 22 | /// 23 | /// A description of the code element. 24 | public string Description { get; set; } = String.Empty; 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | public CodeElementAttribute(string componentName) 30 | { 31 | #if NET20 || PORTABLE2 32 | if (String.IsNullOrEmpty(componentName) || componentName.Trim() == String.Empty) 33 | #else 34 | if (String.IsNullOrWhiteSpace(componentName)) 35 | #endif 36 | throw new ArgumentNullException(nameof(componentName)); 37 | 38 | this.ComponentName = componentName; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0007-invoke-adr-config-executable-to-get-configuration.md: -------------------------------------------------------------------------------- 1 | # 7. Invoke adr-config executable to get configuration 2 | 3 | Date: 2016-12-17 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | Packagers (e.g. Homebrew developers) want to configure adr-tools to match the conventions of their installation. 12 | 13 | Currently, this is done by sourcing a file `config.sh`, which should sit beside the `adr` executable. 14 | 15 | This name is too common. 16 | 17 | The `config.sh` file is not executable, and so doesn't belong in a bin directory. 18 | 19 | ## Decision 20 | 21 | Replace `config.sh` with an executable, named `adr-config` that outputs configuration. 22 | 23 | Each script in ADR Tools will eval the output of `adr-config` to configure itself. 24 | 25 | ## Consequences 26 | 27 | Configuration within ADR Tools is a little more complicated. 28 | 29 | Packagers can write their own implementation of `adr-config` that outputs configuration that matches the platform's installation conventions, and deploy it next to the `adr` script. 30 | 31 | To make development easier, the implementation of `adr-config` in the project's src/ directory will output configuration that lets the tool to run from the src/ directory without any installation step. (Packagers should not include this script in a deployable package.) 32 | 33 | --- 34 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/adrs/0008-use-iso-8601-format-for-dates.md: -------------------------------------------------------------------------------- 1 | # 8. Use ISO 8601 Format for Dates 2 | 3 | Date: 2017-02-21 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | `adr-tools` seeks to communicate the history of architectural decisions of a 12 | project. An important component of the history is the time at which a decision 13 | was made. 14 | 15 | To communicate effectively, `adr-tools` should present information as 16 | unambiguously as possible. That means that culture-neutral data formats should 17 | be preferred over culture-specific formats. 18 | 19 | Existing `adr-tools` deployments format dates as `dd/mm/yyyy` by default. That 20 | formatting is common formatting in the United Kingdom (where the `adr-tools` 21 | project was originally written), but is easily confused with the `mm/dd/yyyy` 22 | format preferred in the United States. 23 | 24 | The default date format may be overridden by setting `ADR_DATE` in `config.sh`. 25 | 26 | ## Decision 27 | 28 | `adr-tools` will use the ISO 8601 format for dates: `yyyy-mm-dd` 29 | 30 | ## Consequences 31 | 32 | Dates are displayed in a standard, culture-neutral format. 33 | 34 | The UK-style and ISO 8601 formats can be distinguished by their separator 35 | character. The UK-style dates used a slash (`/`), while the ISO dates use a 36 | hyphen (`-`). 37 | 38 | Prior to this decision, `adr-tools` was deployed using the UK format for dates. 39 | After adopting the ISO 8601 format, existing deployments of `adr-tools` must do 40 | one of the following: 41 | 42 | * Accept mixed formatting of dates within their documentation library. 43 | * Update existing documents to use ISO 8601 dates by running `adr upgrade-repository` 44 | -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0003-single-command-with-subcommands.md: -------------------------------------------------------------------------------- 1 | # 3. Single command with subcommands 2 | 3 | Date: 2016-02-12 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | The tool provides a number of related commands to create 12 | and manipulate architecture decision records. 13 | 14 | How can the user find out about the commands that are available? 15 | 16 | ## Decision 17 | 18 | The tool defines a single command, called `adr`. 19 | 20 | The first argument to `adr` (the subcommand) specifies the 21 | action to perform. Further arguments are interpreted by the 22 | subcommand. 23 | 24 | Running `adr` without any arguments lists the available 25 | subcommands. 26 | 27 | Subcommands are implemented as scripts in the same 28 | directory as the `adr` script. E.g. the subcommand `new` is 29 | implemented as the script `adr-new`, the subcommand `help` 30 | as the script `adr-help` and so on. 31 | 32 | Helper scripts that are part of the implementation but not 33 | subcommands follow a different naming convention, so that 34 | subcommands can be listed by filtering and transforming script 35 | file names. 36 | 37 | ## Consequences 38 | 39 | Users can more easily explore the capabilities of the tool. 40 | 41 | Users are already used to this style of command-line tool. For 42 | example, Git works this way. 43 | 44 | Each subcommand can be implemented in the most appropriate 45 | language. 46 | 47 | --- 48 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). 49 | -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0006-packaging-and-distribution-in-other-version-control-repositories.md: -------------------------------------------------------------------------------- 1 | # 6. Packaging and distribution in other version control repositories 2 | 3 | Date: 2016-02-16 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | Users want to install adr-tools with their preferred package 12 | manager. For example, Ubuntu users use `apt`, RedHat users use 13 | `yum` and Mac OS X users use [Homebrew](http://brew.sh). 14 | 15 | The developers of `adr-tools` don't know how, nor have permissions, 16 | to use all these packaging and distribution systems. Therefore packaging 17 | and distribution must be done by "downstream" parties. 18 | 19 | The developers of the tool should not favour any one particular 20 | packaging and distribution solution. 21 | 22 | ## Decision 23 | 24 | The `adr-tools` project will not contain any packaging or 25 | distribution scripts and config. 26 | 27 | Packaging and distribution will be managed by other projects in 28 | separate version control repositories. 29 | 30 | ## Consequences 31 | 32 | The git repo of this project will be simpler. 33 | 34 | Eventually, users will not have to use Git to get the software. 35 | 36 | We will have to tag releases in the `adr-tools` repository so that 37 | packaging projects know what can be published and how it should be 38 | identified. 39 | 40 | We will document how users can install the software in this 41 | project's README file. 42 | 43 | --- 44 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /Structurizr.Cecil/Util/TypeReferenceExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | using Mono.Cecil; 5 | 6 | namespace Structurizr.Cecil 7 | { 8 | /// 9 | /// Provides extension methods for . 10 | /// 11 | public static class TypeReferenceExtensions 12 | { 13 | /// 14 | /// Gets the assembly qualified name of the type. 15 | /// 16 | /// The instance representing the type. 17 | /// The assembly qualified name of the type. 18 | public static string GetAssemblyQualifiedName(this TypeReference type) 19 | { 20 | string typeName; 21 | 22 | if (type.IsGenericInstance) 23 | { 24 | var genericInstance = (GenericInstanceType)type; 25 | typeName = String.Format("{0}.{1}[{2}]", 26 | genericInstance.Namespace, 27 | type.Name, 28 | String.Join(",", 29 | genericInstance.GenericArguments.Select(p => p.GetAssemblyQualifiedName()).ToArray() 30 | )); 31 | } 32 | else 33 | { 34 | typeName = type.FullName; 35 | } 36 | 37 | var scope = type.Scope as AssemblyNameReference; 38 | if (scope != null) 39 | { 40 | return typeName + ", " + scope.FullName; 41 | } 42 | else if (type.Scope != null) 43 | { 44 | return typeName + ", " + type.Module.Assembly.FullName; 45 | } 46 | 47 | return typeName; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Structurizr.PlantUML/IO/C4PlantUML/ModelExtensions/RelationshipViewExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizr.IO.C4PlantUML.ModelExtensions 2 | { 3 | public static class RelationshipViewExtensions 4 | { 5 | /// 6 | /// Get a direction of the relation which should be used in a specific C4PlantUML views 7 | /// 8 | /// 9 | /// returns true if it is defined via the view specific RelationshipView and false if it is defined via the underlying Relationship 10 | /// 11 | public static string GetDirection(this RelationshipView relationshipView, out bool viewSpecific) 12 | { 13 | string value = DirectionValues.NotSet; 14 | if (relationshipView.Properties?.TryGetValue(Properties.Direction, out value) == true) 15 | { 16 | viewSpecific = true; 17 | return value; 18 | } 19 | 20 | viewSpecific = false; 21 | value = relationshipView.Relationship.GetDirection(); 22 | return value; 23 | } 24 | 25 | /// 26 | /// Set a direction of the relation which should be used in a specific C4PlantUML views 27 | /// 28 | /// 29 | /// one of 30 | public static void SetDirection(this RelationshipView relationshipView, string direction) 31 | { 32 | if (string.IsNullOrWhiteSpace(direction)) // direction DirectionValues.NotSet 33 | relationshipView.Properties.Remove(Properties.Direction); 34 | else 35 | relationshipView.Properties[Properties.Direction] = direction; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Structurizr.Roslyn/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Structurizr.Roslyn/Structurizr.Roslyn.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | Roslyn based software architecture analysis for Structurizr.Core 11 | Structurizr Limited 12 | Copyright 2017 13 | https://opensource.org/licenses/Apache-2.0 14 | https://structurizr.com 15 | https://github.com/structurizr/dotnet 16 | 17 | 18 | 19 | net46 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Structurizr.Examples/Documentation/adr/0008-use-iso-8601-format-for-dates.md: -------------------------------------------------------------------------------- 1 | # 8. Use ISO 8601 Format for Dates 2 | 3 | Date: 2017-02-21 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | `adr-tools` seeks to communicate the history of architectural decisions of a 12 | project. An important component of the history is the time at which a decision 13 | was made. 14 | 15 | To communicate effectively, `adr-tools` should present information as 16 | unambiguously as possible. That means that culture-neutral data formats should 17 | be preferred over culture-specific formats. 18 | 19 | Existing `adr-tools` deployments format dates as `dd/mm/yyyy` by default. That 20 | formatting is common formatting in the United Kingdom (where the `adr-tools` 21 | project was originally written), but is easily confused with the `mm/dd/yyyy` 22 | format preferred in the United States. 23 | 24 | The default date format may be overridden by setting `ADR_DATE` in `config.sh`. 25 | 26 | ## Decision 27 | 28 | `adr-tools` will use the ISO 8601 format for dates: `yyyy-mm-dd` 29 | 30 | ## Consequences 31 | 32 | Dates are displayed in a standard, culture-neutral format. 33 | 34 | The UK-style and ISO 8601 formats can be distinguished by their separator 35 | character. The UK-style dates used a slash (`/`), while the ISO dates use a 36 | hyphen (`-`). 37 | 38 | Prior to this decision, `adr-tools` was deployed using the UK format for dates. 39 | After adopting the ISO 8601 format, existing deployments of `adr-tools` must do 40 | one of the following: 41 | 42 | * Accept mixed formatting of dates within their documentation library. 43 | * Update existing documents to use ISO 8601 dates by running `adr upgrade-repository` 44 | 45 | --- 46 | This Architecture Decision Record (ADR) was written by Nat Pryce as a part of [adr-tools](https://github.com/npryce/adr-tools), and is reproduced here under the [Creative Commons Attribution 4.0 International (CC BY 4.0) license](https://creativecommons.org/licenses/by/4.0/). -------------------------------------------------------------------------------- /Structurizr.ActiveDirectory/AzureActiveDirectoryStructurizrClient.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Api; 2 | using System; 3 | using System.Net.Http; 4 | using Microsoft.IdentityModel.Clients.ActiveDirectory; 5 | using System.Globalization; 6 | using System.Net.Http.Headers; 7 | 8 | namespace Structurizr.ActiveDirectory 9 | { 10 | 11 | /// 12 | /// An extension of the regular StructurizrClient that allows an Active Directory authentication context 13 | /// to be injected into the API requests. Use this if your Structurizr on-premises installation 14 | /// resides behind Active Directory. 15 | /// 16 | public class AzureActiveDirectoryStructurizrClient : StructurizrClient 17 | { 18 | 19 | private AuthenticationResult authenticationResult; 20 | 21 | public AzureActiveDirectoryStructurizrClient(string apiUrl, string apiKey, string apiSecret, 22 | string azureActiveDirectoryInstance, string activeDirectoryTenant, string clientId, string clientKey, string resourceId) : base(apiUrl, apiKey, apiSecret) 23 | { 24 | string authority = String.Format(CultureInfo.InvariantCulture, azureActiveDirectoryInstance, activeDirectoryTenant); 25 | AuthenticationContext authenticationContext = new AuthenticationContext(authority); 26 | ClientCredential clientCredential = new ClientCredential(clientId, clientKey); 27 | 28 | try 29 | { 30 | authenticationResult = authenticationContext.AcquireTokenAsync(resourceId, clientCredential).Result; 31 | } 32 | catch (AdalException ex) 33 | { 34 | Console.WriteLine(ex.ToString()); 35 | } 36 | 37 | } 38 | 39 | protected override HttpClient createHttpClient() 40 | { 41 | HttpClient httpClient = base.createHttpClient(); 42 | httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken); 43 | 44 | return httpClient; 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Structurizr.PlantUML.Tests/Utilities/UnifyExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Structurizr.IO.PlantUML.Tests.Utilities 6 | { 7 | public static class UnifyExtension 8 | { 9 | public static string UnifyNewLine(this string multiLineText) 10 | { 11 | var nPos = multiLineText.IndexOf('\n'); 12 | var rPos = multiLineText.IndexOf('\r'); 13 | if (rPos==-1 || nPos>=0 && nPos < rPos) 14 | return multiLineText 15 | .Replace("\n\r", "<>") 16 | .Replace("\n", "<>") 17 | .Replace("<>", Environment.NewLine); 18 | 19 | return multiLineText 20 | .Replace("\r\n", "<>") 21 | .Replace("\r", "<>") 22 | .Replace("<>", Environment.NewLine); 23 | } 24 | 25 | /// 26 | /// C4LibraryPlantUmlWriter create some names with hash codes (__[0-9a-f]+) which are dynamic created 27 | /// and has to be harmonized 28 | /// 29 | /// 30 | /// 31 | public static string UnifyHashValues(this string textWithMultipleHashValues) 32 | { 33 | var hashRegex = new Regex("__([0-9a-f]+)"); 34 | var hashToPlaceholder = new Dictionary(); 35 | var placeholderIndex = 0; 36 | 37 | var unified = hashRegex.Replace(textWithMultipleHashValues, m => 38 | { 39 | var orig = m.Groups[1].ToString(); 40 | if (!hashToPlaceholder.TryGetValue(orig, out var placeholder)) 41 | { 42 | placeholder = $"HASH{placeholderIndex++}"; 43 | hashToPlaceholder[orig] = placeholder; 44 | } 45 | 46 | return $"__{placeholder}"; 47 | }); 48 | 49 | return unified; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /Structurizr.Examples/Structurizr.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp1.1 5 | Structurizr.Examples.PlantUML 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Always 18 | 19 | 20 | Always 21 | 22 | 23 | Always 24 | 25 | 26 | Always 27 | 28 | 29 | Always 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | Always 39 | 40 | 41 | Always 42 | 43 | 44 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/Structurizr.AdrTools.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp1.1 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Always 17 | 18 | 19 | Always 20 | 21 | 22 | Always 23 | 24 | 25 | Always 26 | 27 | 28 | Always 29 | 30 | 31 | Always 32 | 33 | 34 | Always 35 | 36 | 37 | Always 38 | 39 | 40 | Always 41 | 42 | 43 | Always 44 | 45 | 46 | -------------------------------------------------------------------------------- /Structurizr.Annotations/UsedByPersonAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the named person uses the component on which this attribute is placed, creating a relationship 7 | /// from the person to the component. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] 10 | public sealed class UsedByPersonAttribute : System.Attribute 11 | { 12 | /// 13 | /// Gets the name of the person who uses the attributed component. 14 | /// 15 | /// The name of the person who uses the attributed component. 16 | public string PersonName { get; } 17 | 18 | /// 19 | /// Gets or sets a description of the relationship from the named person to the attributed component. 20 | /// 21 | /// A string describing how the named person uses the attributed component. 22 | public string Description { get; set; } 23 | 24 | /// 25 | /// Gets or sets a description of the technology used to implement the relationship between the named person 26 | /// and the attributed component. 27 | /// 28 | /// 29 | /// A string describing the technology used for connecting the named person to the attributed component. 30 | /// 31 | public string Technology { get; set; } 32 | 33 | /// 34 | /// Initializes a new instance of the class with the name of the person 35 | /// using the component specified. 36 | /// 37 | /// The name of the person which uses the attributed component. 38 | public UsedByPersonAttribute(string personName) 39 | { 40 | #if NET20 || PORTABLE2 41 | if (String.IsNullOrEmpty(personName) || personName.Trim() == String.Empty) 42 | #else 43 | if (String.IsNullOrWhiteSpace(personName)) 44 | #endif 45 | throw new ArgumentNullException(nameof(personName)); 46 | 47 | this.PersonName = personName; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Structurizr.Cecil/Util/AssemblyDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | 5 | using Mono.Cecil; 6 | 7 | namespace Structurizr.Cecil 8 | { 9 | /// 10 | /// Provides extension methods for . 11 | /// 12 | public static class AssemblyDefinitionExtensions 13 | { 14 | /// 15 | /// Generates a collection of assemblies directly or indirectly referenced by this assembly. 16 | /// 17 | /// The assembly whose references are to be returned. 18 | /// Whether or not to include this assembly in the returned collection. 19 | /// A generated collection of referenced assemblies. 20 | public static IEnumerable EnumerateReferencedAssemblies(this AssemblyDefinition assembly, 21 | bool includeSelf = true) 22 | { 23 | var references = new HashSet(); 24 | 25 | var queue = new Queue(); 26 | queue.Enqueue(assembly); 27 | 28 | if (includeSelf) 29 | { 30 | yield return assembly; 31 | } 32 | 33 | while (queue.Count > 0) 34 | { 35 | var assm = queue.Dequeue(); 36 | 37 | if (references.Contains(assm.MetadataToken)) continue; 38 | references.Add(assm.MetadataToken); 39 | 40 | var refs = from m in assm.Modules from r in m.AssemblyReferences select r; 41 | foreach (var r in refs) 42 | { 43 | AssemblyDefinition refAssm; 44 | try 45 | { 46 | refAssm = assembly.MainModule.AssemblyResolver.Resolve(r); 47 | queue.Enqueue(refAssm); 48 | } 49 | catch (AssemblyResolutionException e) 50 | { 51 | Debug.WriteLine(e); 52 | refAssm = null; 53 | } 54 | 55 | if (refAssm != null) 56 | yield return refAssm; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Structurizr.Annotations/UsedByContainerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the named container uses the component on which this attribute is placed, creating a relationship 7 | /// from the container to the component. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] 10 | public sealed class UsedByContainerAttribute : System.Attribute 11 | { 12 | /// 13 | /// Gets the name of the container which uses the attributed component. 14 | /// 15 | /// The name of the container which uses the attributed component. 16 | public string ContainerName { get; } 17 | 18 | /// 19 | /// Gets or sets a description of the relationship from the named container to the attributed component. 20 | /// 21 | /// A string describing how the named container uses the attributed component. 22 | public string Description { get; set; } = ""; 23 | 24 | /// 25 | /// Gets or sets a description of the technology used to implement the relationship between the named container 26 | /// and the attributed component. 27 | /// 28 | /// 29 | /// A string describing the technology used for connecting the named container to the attributed component. 30 | /// 31 | public string Technology { get; set; } = ""; 32 | 33 | /// 34 | /// Initializes a new instance of the class with the name of the 35 | /// container using the component specified. 36 | /// 37 | /// The name of the container which uses the attributed component. 38 | public UsedByContainerAttribute(string containerName) 39 | { 40 | #if NET20 || PORTABLE2 41 | if (String.IsNullOrEmpty(containerName) || containerName.Trim() == String.Empty) 42 | #else 43 | if (String.IsNullOrWhiteSpace(containerName)) 44 | #endif 45 | throw new ArgumentNullException(nameof(containerName)); 46 | 47 | this.ContainerName = containerName; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Structurizr.Annotations/UsesContainerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the named container is used by the component on which this attribute is placed, creating a 7 | /// relationship from the component to the container. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] 10 | public sealed class UsesContainerAttribute : System.Attribute 11 | { 12 | /// 13 | /// Gets the name of the container used by the attributed component. 14 | /// 15 | /// The name of the container which is used by the attributed component. 16 | public string ContainerName { get; } 17 | 18 | /// 19 | /// Gets or sets a description of the relationship from the attributed component to the named container. 20 | /// 21 | /// A string describing how the named container is used by the attributed component. 22 | public string Description { get; set; } = ""; 23 | 24 | /// 25 | /// Gets or sets a description of the technology used to implement the relationship between the attributed 26 | /// component to the named container. 27 | /// 28 | /// 29 | /// A string describing the technology used for connecting the attributed component to the named container. 30 | /// 31 | public string Technology { get; set; } = ""; 32 | 33 | /// 34 | /// Initializes a new instance of the class with the name of the 35 | /// container being used by the attributed component. 36 | /// 37 | /// The name of the container which is used by the attributed component. 38 | public UsesContainerAttribute(string containerName) 39 | { 40 | #if NET20 || PORTABLE2 41 | if (String.IsNullOrEmpty(containerName) || containerName.Trim() == String.Empty) 42 | #else 43 | if (String.IsNullOrWhiteSpace(containerName)) 44 | #endif 45 | throw new ArgumentNullException(nameof(containerName)); 46 | 47 | this.ContainerName = containerName; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Structurizr.ActiveDirectory/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizr.ActiveDirectory 2 | { 3 | class Program 4 | { 5 | 6 | private const long WorkspaceId = 1; 7 | private const string ApiUrl = ""; 8 | private const string ApiKey = ""; 9 | private const string ApiSecret = ""; 10 | 11 | private const string AzureActiveDirectoryInstance = "https://login.windows.net/{0}"; 12 | private const string AzureActiveDirectoryTenant = ""; // e.g. something.onmicrosoft.com 13 | private const string AzureActiveDirectoryClientId = ""; // the "Application ID" of the Structurizr client application 14 | private const string AzureActiveDirectoryClientKey = ""; // a key for the Structurizr client application 15 | private const string AzureActiveDirectoryResourceId = ""; // the "Application ID" of the Structurizr on-premises web application 16 | 17 | static void Main() 18 | { 19 | Workspace workspace = new Workspace("Getting Started", "This is a model of my software system."); 20 | Model model = workspace.Model; 21 | 22 | Person user = model.AddPerson("User", "A user of my software system."); 23 | SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); 24 | user.Uses(softwareSystem, "Uses"); 25 | 26 | ViewSet viewSet = workspace.Views; 27 | SystemContextView contextView = viewSet.CreateSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); 28 | contextView.AddAllSoftwareSystems(); 29 | contextView.AddAllPeople(); 30 | 31 | Styles styles = viewSet.Configuration.Styles; 32 | styles.Add(new ElementStyle(Tags.SoftwareSystem) { Background = "#1168bd", Color = "#ffffff" }); 33 | styles.Add(new ElementStyle(Tags.Person) { Background = "#08427b", Color = "#ffffff", Shape = Shape.Person }); 34 | 35 | AzureActiveDirectoryStructurizrClient structurizrClient = new AzureActiveDirectoryStructurizrClient( 36 | ApiUrl, ApiKey, ApiSecret, 37 | AzureActiveDirectoryInstance, AzureActiveDirectoryTenant, AzureActiveDirectoryClientId, AzureActiveDirectoryClientKey, AzureActiveDirectoryResourceId 38 | ); 39 | structurizrClient.PutWorkspace(WorkspaceId, workspace); 40 | } 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /Structurizr.Annotations/UsedBySoftwareSystemAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the named software system uses the component on which this attribute is placed, creating a 7 | /// relationship from the software system to the component. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] 10 | public sealed class UsedBySoftwareSystemAttribute : System.Attribute 11 | { 12 | /// 13 | /// Gets the name of the software system who uses the attributed component. 14 | /// 15 | /// The name of the software system who uses the attributed component. 16 | public string SoftwareSystemName { get; } 17 | 18 | /// 19 | /// Gets or sets a description of the relationship from the named software system to the attributed component. 20 | /// 21 | /// A string describing how the named software system uses the attributed component. 22 | public string Description { get; set; } 23 | 24 | /// 25 | /// Gets or sets a description of the technology used to implement the relationship between the named software 26 | /// system and the attributed component. 27 | /// 28 | /// 29 | /// A string describing the technology used for connecting the named software system to the attributed 30 | /// component. 31 | /// 32 | public string Technology { get; set; } 33 | 34 | /// 35 | /// Initializes a new instance of the class with the name of the 36 | /// software system using the component specified. 37 | /// 38 | /// The name of the software system which uses the attributed component. 39 | public UsedBySoftwareSystemAttribute(string softwareSystemName) 40 | { 41 | #if NET20 || PORTABLE2 42 | if (String.IsNullOrEmpty(softwareSystemName) || softwareSystemName.Trim() == String.Empty) 43 | #else 44 | if (String.IsNullOrWhiteSpace(softwareSystemName)) 45 | #endif 46 | throw new ArgumentNullException(nameof(softwareSystemName)); 47 | 48 | this.SoftwareSystemName = softwareSystemName; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | #*.ts text 7 | 8 | # Shell scripts should always have lf line endings 9 | *.sh text eol=lf 10 | sources.list text eol=lf 11 | 12 | #### Visual Studio specific stuff 13 | 14 | ############################################################################### 15 | # Set default behavior for command prompt diff. 16 | # 17 | # This is need for earlier builds of msysgit that does not have it on by 18 | # default for csharp files. 19 | # Note: This is only used by command line 20 | ############################################################################### 21 | *.cs diff=csharp 22 | 23 | ############################################################################### 24 | # Set the merge driver for project and solution files 25 | # 26 | # Merging from the command prompt will add diff markers to the files if there 27 | # are conflicts (Merging from VS is not affected by the settings below, in VS 28 | # the diff markers are never inserted). Diff markers may cause the following 29 | # file extensions to fail to load in VS. An alternative would be to treat 30 | # these files as binary and thus will always conflict and require user 31 | # intervention with every merge. To do so, just comment the entries below and 32 | # uncomment the group further below 33 | ############################################################################### 34 | *.sln text eol=crlf 35 | *.csproj text eol=crlf 36 | *.vbproj text eol=crlf 37 | *.vcxproj text eol=crlf 38 | *.vcproj text eol=crlf 39 | *.dbproj text eol=crlf 40 | *.fsproj text eol=crlf 41 | *.lsproj text eol=crlf 42 | *.wixproj text eol=crlf 43 | *.modelproj text eol=crlf 44 | *.sqlproj text eol=crlf 45 | *.wmaproj text eol=crlf 46 | 47 | *.xproj text eol=crlf 48 | *.props text eol=crlf 49 | *.filters text eol=crlf 50 | *.vcxitems text eol=crlf 51 | 52 | *.sln merge=binary 53 | *.csproj merge=binary 54 | *.vbproj merge=binary 55 | *.vcxproj merge=binary 56 | *.vcproj merge=binary 57 | *.dbproj merge=binary 58 | *.fsproj merge=binary 59 | *.lsproj merge=binary 60 | *.wixproj merge=binary 61 | *.modelproj merge=binary 62 | *.sqlproj merge=binary 63 | *.wmaproj merge=binary 64 | 65 | *.xproj merge=binary 66 | *.props merge=binary 67 | *.filters merge=binary 68 | *.vcxitems merge=binary 69 | 70 | -------------------------------------------------------------------------------- /Structurizr.Annotations/UsesSoftwareSystemAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the named software system is used by the component on which this attribute is placed, creating a 7 | /// relationship from the component to the software system. 8 | /// 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true)] 10 | public sealed class UsesSoftwareSystemAttribute : System.Attribute 11 | { 12 | /// 13 | /// Gets the name of the software system used by the attributed component. 14 | /// 15 | /// The name of the software system which is used by the attributed component. 16 | public string SoftwareSystemName { get; } 17 | 18 | /// 19 | /// Gets or sets a description of the relationship from the attributed component to the named software system. 20 | /// 21 | /// A string describing how the named software system is used by the attributed component. 22 | public string Description { get; set; } 23 | 24 | /// 25 | /// Gets or sets a description of the technology used to implement the relationship between the named software 26 | /// system and the attributed component. 27 | /// 28 | /// 29 | /// A string describing the technology used for connecting the attributed component to the named software 30 | /// system. 31 | /// 32 | public string Technology { get; set; } 33 | 34 | /// 35 | /// Initializes a new instance of the class with the name of the 36 | /// software system used by the component specified. 37 | /// 38 | /// 39 | /// The name of the software system which is used by the attributed component. 40 | /// 41 | public UsesSoftwareSystemAttribute(string softwareSystemName) 42 | { 43 | #if NET20 || PORTABLE2 44 | if (String.IsNullOrEmpty(softwareSystemName) || softwareSystemName.Trim() == String.Empty) 45 | #else 46 | if (String.IsNullOrWhiteSpace(softwareSystemName)) 47 | #endif 48 | throw new ArgumentNullException(nameof(softwareSystemName)); 49 | 50 | this.SoftwareSystemName = softwareSystemName; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Structurizr.Annotations/UsesComponentAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Annotations 4 | { 5 | /// 6 | /// Specifies that the type of the attributed field, property, or parameter is a component used by the type to which 7 | /// the attributed field, property, or parameter belongs. 8 | /// 9 | /// 10 | /// 11 | /// In documentation for this annotation, the "attributed component" refers to the type containing the field, 12 | /// property, or parameter on which the attribute is placed. The "used component" is the component specified by the 13 | /// type of the field, property, or parameter. 14 | /// 15 | /// 16 | [AttributeUsage( 17 | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter, 18 | Inherited = false, 19 | AllowMultiple = false)] 20 | public sealed class UsesComponentAttribute : System.Attribute 21 | { 22 | /// 23 | /// Gets a description of the relationship from the attributed component to the used component. 24 | /// 25 | /// A string describing how the used component is used by the attributed component. 26 | public string Description { get; } 27 | 28 | /// 29 | /// Gets or sets a description of the technology used to implement the relationship between the attributed 30 | /// component to the used component. 31 | /// 32 | /// 33 | /// A string describing the technology used for connecting the attributed component to the used component. 34 | /// 35 | public string Technology { get; set; } = ""; 36 | 37 | /// 38 | /// Initializes a new instance of the class with a description of the 39 | /// relationship from the attributed component to the used component. 40 | /// 41 | /// 42 | /// A string describing how the used component is used by the attributed component. 43 | /// 44 | public UsesComponentAttribute(string description) 45 | { 46 | #if NET20 || PORTABLE2 47 | if (String.IsNullOrEmpty(description) || description.Trim() == String.Empty) 48 | #else 49 | if (String.IsNullOrWhiteSpace(description)) 50 | #endif 51 | throw new ArgumentNullException(nameof(description)); 52 | 53 | this.Description = description; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Structurizr.Annotations/Structurizr.Annotations.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.9.2 7 | 8 | 9 | 10 | Annotations to maintain the C4 model for software architecture in code 11 | Structurizr Annotations lets you use .NET attributes to declare components and their relationships using the C4 model for software architecture. 12 | Structurizr Limited 13 | Copyright 2017 14 | https://opensource.org/licenses/Apache-2.0 15 | https://structurizr.com 16 | https://github.com/structurizr/dotnet 17 | 18 | 19 | 20 | netstandard1.0;net20;portable40-net40+sl4+win8+wp7;portable40-net40+sl5+win8+wp8+wpa81 21 | bin\$(Configuration)\$(TargetFramework)\Structurizr.Annotations.xml 22 | 1.6.0 23 | 24 | 25 | 26 | .NETPortable 27 | v4.0 28 | Profile2 29 | $(DefineConstants);PORTABLE2 30 | 31 | 32 | 33 | .NETPortable 34 | v4.0 35 | Profile328 36 | $(DefineConstants);PORTABLE328 37 | 38 | 39 | 40 | .NETFramework 41 | v2.0 42 | $(DefineConstants);NET20 43 | $(windir)\Microsoft.NET\Framework\v2.0.50727 44 | 45 | 46 | 47 | 48 | 4.1.0 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/ITypeRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Structurizr.Analysis 5 | { 6 | 7 | /// 8 | /// Declares an interface for a repository of type information from Mono.Cecil. 9 | /// 10 | public interface ITypeRepository 11 | { 12 | 13 | /// 14 | /// Gets the root namespace of the types stored in the repository. 15 | /// 16 | /// The name of the root namespace of the types in the repository. 17 | string Namespace { get; } 18 | 19 | /// 20 | /// Gets all of the types stored in this type repository. 21 | /// 22 | /// 23 | /// A collection of objects, or an empty enumeration if no types were found. 24 | /// 25 | IEnumerable GetAllTypes(); 26 | 27 | /// 28 | /// Gets a single type from the repository by name. 29 | /// 30 | /// The name of the type to return. 31 | /// 32 | /// A for the specified type, or if the type could not 33 | /// be found. 34 | /// 35 | Type GetType(string type); 36 | 37 | /// 38 | /// Gets all of the types in the repository that are referenced by the named type. 39 | /// 40 | /// The name of the type whose references are to be returned. 41 | /// 42 | /// A collection of objects representing the types referenced by the type named by 43 | /// , or an empty enumeration if no types were found. 44 | /// 45 | IEnumerable GetReferencedTypes(string type); 46 | 47 | /// 48 | /// Gets the type category (class, interface, etc.) of the named type. 49 | /// 50 | /// The name of the type being categorized. 51 | /// The type category of the type if not "class" or "struct", otherwise . 52 | string FindCategory(string typeName); 53 | 54 | /// 55 | /// Gets the access modifier of the named type. 56 | /// 57 | /// The name of the type whose access modifier is being looked up. 58 | /// 59 | /// The type's access modifier ("public" or "internal") if the type is found, otherwise . 60 | /// 61 | string FindVisibility(string typeName); 62 | 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/ITypeRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using Mono.Cecil; 4 | 5 | namespace Structurizr.Analysis 6 | { 7 | /// 8 | /// Declares an interface for a repository of type information from Mono.Cecil. 9 | /// 10 | public interface ITypeRepository 11 | { 12 | /// 13 | /// Gets the root namespace of the types stored in the repository. 14 | /// 15 | /// The name of the root namespace of the types in the repository. 16 | string Namespace { get; } 17 | 18 | /// 19 | /// Gets all of the types stored in this type repository. 20 | /// 21 | /// 22 | /// A collection of objects, or an empty enumeration if no types were found. 23 | /// 24 | IEnumerable GetAllTypes(); 25 | 26 | /// 27 | /// Gets a single type from the repository by name. 28 | /// 29 | /// The name of the type to return. 30 | /// 31 | /// A for the specified type, or if the type could not 32 | /// be found. 33 | /// 34 | TypeDefinition GetType(string typeName); 35 | 36 | /// 37 | /// Gets all of the types in the repository that are referenced by the named type. 38 | /// 39 | /// The name of the type whose references are to be returned. 40 | /// 41 | /// A collection of objects representing the types referenced by the type named by 42 | /// , or an empty enumeration if no types were found. 43 | /// 44 | IEnumerable GetReferencedTypes(string type); 45 | 46 | /// 47 | /// Gets the type category (class, interface, etc.) of the named type. 48 | /// 49 | /// The name of the type being categorized. 50 | /// The type category of the type if not "class" or "struct", otherwise . 51 | string FindCategory(string typeName); 52 | 53 | /// 54 | /// Gets the access modifier of the named type. 55 | /// 56 | /// The name of the type whose access modifier is being looked up. 57 | /// 58 | /// The type's access modifier ("public" or "internal") if the type is found, otherwise . 59 | /// 60 | string FindVisibility(string typeName); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.9.3 (22nd November 2019) 4 | 5 | - Fixes a bug that allows relationships to be created between parents and children. 6 | - Added support for perspectives on elements and relationships. 7 | - Add new C4PlantUMLWriter with C4-PlantUML support (issue #47). 8 | - Added support for element stroke colours. 9 | 10 | ## 0.9.2 (16th September 2019) 11 | 12 | - Fixes a bug where element and relationship positioning is lost when workspaces are merged. 13 | 14 | ## 0.9.1 (10th September 2019) 15 | 16 | - The terminology for relationships can now be customised. 17 | - Added support for icons on element styles. 18 | - Top-level deployment nodes can now be given an environment property, to represent which deployment environment they belong to (e.g. "Development", "Live", etc). 19 | - Relationships can no longer be created between container instances (__breaking change__). 20 | - Fixed issue #40 (WorkspaceUtils does not load containers' properties). 21 | - Provided a way to customize the sort order when displaying the list of views. 22 | - Added the ability to lock and unlock workspaces, to prevent concurrent updates. 23 | - Fixes a bug with the PlantUML writer, where relationships were sorted incorrectly (alphabetically, rather than numerically). 24 | 25 | ## 0.9.0 (8th November 2018) 26 | 27 | - Added the ability to specify users who should have read-write or read-only workspace access, via the ```workspace.Configuration.AddUser(username, role)``` method. 28 | 29 | ## 0.8.3 (24th October 2018) 30 | 31 | - Fixes a bug where client-side encrypted workspaces could not be created. 32 | 33 | ## 0.8.2 (23rd October 2018) 34 | 35 | - Added name-value properties to relationships. 36 | - Added the ability to define animations on the static structure diagrams. 37 | - Removed support for colours in the corporate branding feature (__breaking change__). 38 | 39 | ## 0.8.0 40 | 41 | - Added validation for hex colour codes (on ElementStyle and RelationshipStyle) 42 | - Removed the "groups" property of documentation sections (__breaking change__). 43 | - Added support for the HTTP-based health checks feature. 44 | - Added an ```EndParallelSequence(boolean)``` method to the ```DynamicView``` class, which allows sequence numbering to continue. 45 | - Added support for decision records. 46 | - Moved PlantUML support to a separate Structurizr.PlantUML project. 47 | - Separated the API client from the core library, and created a new Structurizr.Client project. 48 | - Renamed the "type" property on Section to "title". 49 | 50 | ## 0.7.2 51 | 52 | - Added some new shapes: web browser, mobile device (portrait and landscape), and robot. 53 | - Added the ability to enable/disable the enterprise boundary on system landscape and system context views. 54 | - Added the ability to customise the terminology used when rendering views. 55 | - Added the ability to hide element metadata and/or descriptions. 56 | - Bug fixes and performance enhancements. -------------------------------------------------------------------------------- /docs/plantuml.md: -------------------------------------------------------------------------------- 1 | # PlantUML 2 | 3 | Structurizr for .NET also includes a simple exporter that can create diagram definitions compatible with [PlantUML](http://www.plantuml.com). The following diagram types are supported: 4 | 5 | - Enterprise Context 6 | - System Context 7 | - Container 8 | - Component 9 | - Dynamic 10 | - Deployment 11 | 12 | Simply create your software architecture model and views as usual, and use the [PlantUMLWriter](https://github.com/structurizr/dotnet/blob/master/Structurizr.Core/IO/PlantUML/PlantUMLWriter.cs) class to export the views. [For example](https://github.com/structurizr/dotnet/blob/master/Structurizr.Examples/PlantUML.cs): 13 | 14 | ```c# 15 | Workspace workspace = new Workspace("Getting Started", "This is a model of my software system."); 16 | Model model = workspace.Model; 17 | 18 | Person user = model.AddPerson("User", "A user of my software system."); 19 | SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); 20 | user.Uses(softwareSystem, "Uses"); 21 | 22 | ViewSet views = workspace.Views; 23 | SystemContextView contextView = views.CreateSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); 24 | contextView.AddAllSoftwareSystems(); 25 | contextView.AddAllPeople(); 26 | 27 | StringWriter stringWriter = new StringWriter(); 28 | PlantUMLWriter plantUMLWriter = new PlantUMLWriter(); 29 | plantUMLWriter.Write(workspace, stringWriter); 30 | Console.WriteLine(stringWriter.ToString()); 31 | ``` 32 | 33 | This code will generate and output a PlantUML diagram definition that looks like this: 34 | 35 | ``` 36 | @startuml 37 | title Software System - System Context 38 | caption An example of a System Context diagram. 39 | component "Software System" <> as 2 40 | actor "User" <> as 1 41 | 1 ..> 2 : Uses 42 | @enduml 43 | ``` 44 | 45 | If you copy/paste this into [PlantUML online](http://www.plantuml.com/plantuml/), you will get something like this: 46 | 47 | ![A simple PlantUML diagram](images/plantuml-getting-started.png) 48 | 49 | ## Benefits of using PlantUML with Structurizr 50 | 51 | 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: 52 | 53 | 1. Rather than looking after a collection of disjointed PlantUML diagram definitions, you can create many PlantUML diagrams from a single model and keep them all up to date easily, especially if integrated with your continuous build server and build pipeline. 54 | 1. The naming of elements and the definition of relationships between elements _remains consistent across diagrams_. 55 | 1. The software architecture model at the component level can be created by extracting components from a codebase, using _static analysis and reflection techniques_. 56 | -------------------------------------------------------------------------------- /Structurizr.Examples/C4PlantUML.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Structurizr.IO.C4PlantUML; 5 | using Structurizr.IO.C4PlantUML.ModelExtensions; 6 | 7 | namespace Structurizr.Examples 8 | { 9 | /// 10 | /// An example of how to use the C4PlantUML writer. Run this program and copy/paste 11 | /// the output into http://www.plantuml.com/plantuml/ 12 | /// 13 | public class C4PlantUML 14 | { 15 | static void Main() 16 | { 17 | Workspace workspace = new Workspace("Getting Started", "This is a model of my software system."); 18 | Model model = workspace.Model; 19 | 20 | model.Enterprise = new Enterprise("Some Enterprise"); 21 | 22 | Person user = model.AddPerson("User", "A user of my software system."); 23 | SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); 24 | var userUsesSystemRelation = user.Uses(softwareSystem, "Uses"); 25 | // a direction could be added to relation (active in all views) 26 | // userUsesSystemRelation.SetDirection(DirectionValues.Right); 27 | 28 | ViewSet views = workspace.Views; 29 | SystemContextView contextView = views.CreateSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); 30 | contextView.AddAllSoftwareSystems(); 31 | contextView.AddAllPeople(); 32 | 33 | // C4PlantUMLWriter support view specific directions too, e.g. "User" should be left of "Software System" only in this view 34 | contextView.Relationships 35 | .First(rv => rv.Relationship.SourceId == user.Id && rv.Relationship.DestinationId == softwareSystem.Id) 36 | .SetDirection(DirectionValues.Right); 37 | 38 | using (var stringWriter = new StringWriter()) 39 | { 40 | var plantUmlWriter = new C4PlantUmlWriter(); 41 | plantUmlWriter.Write(workspace, stringWriter); 42 | Console.WriteLine(stringWriter.ToString()); 43 | } 44 | 45 | 46 | Container webApplication = softwareSystem.AddContainer("Web Application", "Delivers content", "Java and spring MVC"); 47 | Container database = softwareSystem.AddContainer("Database", "Stores information", "Relational Database Schema"); 48 | // Additional mark it as database 49 | database.SetIsDatabase(true); 50 | user.Uses(webApplication, "uses", "HTTP"); 51 | webApplication.Uses(database, "Reads from and writes to", "JDBC").SetDirection(DirectionValues.Right); 52 | 53 | var containerView = views.CreateContainerView(softwareSystem, "containers", ""); 54 | containerView.AddAllElements(); 55 | 56 | using (var stringWriter = new StringWriter()) 57 | { 58 | var plantUmlWriter = new C4PlantUmlWriter(); 59 | plantUmlWriter.Write(containerView, stringWriter); 60 | Console.WriteLine(stringWriter.ToString()); 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Structurizr.Reflection.Examples/StructurizrAnnotations.cs: -------------------------------------------------------------------------------- 1 | using Structurizr.Analysis; 2 | using Structurizr.Api; 3 | 4 | namespace Structurizr.Examples 5 | { 6 | 7 | /// 8 | /// A small example that illustrates how to use the Structurizr annotations 9 | /// in conjunction with the StructurizrAnnotationsComponentFinderStrategy. 10 | /// 11 | /// The live workspace is available to view at https://structurizr.com/share/38341 12 | /// 13 | public class StructurizrAnnotations 14 | { 15 | private const string DatabaseTag = "Database"; 16 | 17 | private const long WorkspaceId = 38341; 18 | private const string ApiKey = "key"; 19 | private const string ApiSecret = "secret"; 20 | 21 | static void Main() 22 | { 23 | Workspace workspace = new Workspace("Structurizr for .NET Annotations", "This is a model of my software system."); 24 | Model model = workspace.Model; 25 | 26 | Person user = model.AddPerson("User", "A user of my software system."); 27 | SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); 28 | 29 | Container webApplication = softwareSystem.AddContainer("Web Application", "Provides users with information.", "C#"); 30 | Container database = softwareSystem.AddContainer("Database", "Stores information.", "Relational database schema"); 31 | database.AddTags(DatabaseTag); 32 | 33 | ComponentFinder componentFinder = new ComponentFinder( 34 | webApplication, 35 | "Structurizr.Examples.Annotations", 36 | new StructurizrAnnotationsComponentFinderStrategy() 37 | ); 38 | componentFinder.FindComponents(); 39 | model.AddImplicitRelationships(); 40 | 41 | ViewSet views = workspace.Views; 42 | SystemContextView contextView = views.CreateSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); 43 | contextView.AddAllElements(); 44 | 45 | ContainerView containerView = views.CreateContainerView(softwareSystem, "Containers", "The container diagram from my software system."); 46 | containerView.AddAllElements(); 47 | 48 | ComponentView componentView = views.CreateComponentView(webApplication, "Components", "The component diagram for the web application."); 49 | componentView.AddAllElements(); 50 | 51 | Styles styles = views.Configuration.Styles; 52 | styles.Add(new ElementStyle(Tags.Element) { Color = "#ffffff" }); 53 | styles.Add(new ElementStyle(Tags.SoftwareSystem) { Background = "#1168bd" }); 54 | styles.Add(new ElementStyle(Tags.Container) { Background = "#438dd5" }); 55 | styles.Add(new ElementStyle(Tags.Component) { Background = "#85bbf0", Color = "#000000" }); 56 | styles.Add(new ElementStyle(Tags.Person) { Background = "#08427b", Shape = Shape.Person }); 57 | styles.Add(new ElementStyle(DatabaseTag) { Shape = Shape.Cylinder }); 58 | 59 | StructurizrClient structurizrClient = new StructurizrClient(ApiKey, ApiSecret); 60 | structurizrClient.PutWorkspace(WorkspaceId, workspace); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /Structurizr.Examples/AdrTools.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Structurizr.AdrTools; 3 | using Structurizr.Api; 4 | 5 | namespace Structurizr.Examples 6 | { 7 | 8 | /// 9 | /// An example of how to import architecture decision records from Nat Pryce's adr-tools tool. 10 | /// 11 | /// The live workspace is available to view at https://structurizr.com/share/39459 12 | /// 13 | class AdrTools 14 | { 15 | 16 | private const long WorkspaceId = 39459; 17 | private const string ApiKey = "key"; 18 | private const string ApiSecret = "secret"; 19 | 20 | private const string FileSystemTag = "File System"; 21 | 22 | static void Main() 23 | { 24 | Workspace workspace = new Workspace("adr-tools", "A description of the adr-tools command line utility."); 25 | Model model = workspace.Model; 26 | 27 | Person user = model.AddPerson("User", "Somebody on a software development team."); 28 | SoftwareSystem adrTools = model.AddSoftwareSystem("adr-tools", "A command-line tool for working with Architecture Decision Records (ADRs)."); 29 | adrTools.Url ="https://github.com/npryce/adr-tools"; 30 | 31 | Container adrShellScripts = adrTools.AddContainer("adr", "A command-line tool for working with Architecture Decision Records (ADRs).", "Shell Scripts"); 32 | adrShellScripts.Url = "https://github.com/npryce/adr-tools/tree/master/src"; 33 | Container fileSystem = adrTools.AddContainer("File System", "Stores ADRs, templates, etc.", "File System"); 34 | fileSystem.AddTags(FileSystemTag); 35 | user.Uses(adrShellScripts, "Manages ADRs using"); 36 | adrShellScripts.Uses(fileSystem, "Reads from and writes to"); 37 | model.AddImplicitRelationships(); 38 | 39 | ViewSet views = workspace.Views; 40 | SystemContextView contextView = views.CreateSystemContextView(adrTools, "SystemContext", "The system context diagram for adr-tools."); 41 | contextView.AddAllElements(); 42 | 43 | ContainerView containerView = views.CreateContainerView(adrTools, "Containers", "The container diagram for adr-tools."); 44 | containerView.AddAllElements(); 45 | 46 | DirectoryInfo adrDirectory = new DirectoryInfo("Documentation" + Path.DirectorySeparatorChar + "adr"); 47 | 48 | AdrToolsImporter adrToolsImporter = new AdrToolsImporter(workspace, adrDirectory); 49 | adrToolsImporter.ImportArchitectureDecisionRecords(adrTools); 50 | 51 | Styles styles = views.Configuration.Styles; 52 | styles.Add(new ElementStyle(Tags.Element) { Shape = Shape.RoundedBox, Color = "#ffffff" }); 53 | styles.Add(new ElementStyle(Tags.SoftwareSystem) { Background = "#18ADAD", Color = "#ffffff" }); 54 | styles.Add(new ElementStyle(Tags.Person) { Shape = Shape.Person, Background = "#008282", Color = "#ffffff" }); 55 | styles.Add(new ElementStyle(Tags.Container) { Background = "#6DBFBF" }); 56 | styles.Add(new ElementStyle(FileSystemTag) { Shape = Shape.Folder }); 57 | 58 | StructurizrClient structurizrClient = new StructurizrClient(ApiKey, ApiSecret); 59 | structurizrClient.PutWorkspace(WorkspaceId, workspace); 60 | } 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/NameSuffixTypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Implements a component identification rule based on matching a type name against a case-sensitive suffix. 7 | /// 8 | /// 9 | /// 10 | /// The NameSuffixTypeMatcher class provides component identification by looking for types whose names end with the 11 | /// value of the property. The name comparison is case-sensitive. 12 | /// 13 | /// 14 | public class NameSuffixTypeMatcher : ITypeMatcher 15 | { 16 | /// 17 | /// Gets the string used for suffix comparison for identifying components with this rule. 18 | /// 19 | /// A string used for case-sensitive comparisons against types used by the rule. 20 | public string Suffix { get; private set; } 21 | 22 | /// 23 | /// Gets the description to use for components matching the rule. 24 | /// 25 | /// A string containing the Description property for matching components. 26 | /// 27 | /// 28 | public string Description { get; private set; } 29 | 30 | /// 31 | /// Gets the technology name(s) to use for components matching the rule. 32 | /// 33 | /// A string containing the Technology property for matching components. 34 | /// 35 | /// 36 | public string Technology { get; private set; } 37 | 38 | /// 39 | /// Creates a new instance of using the provided suffix string. 40 | /// 41 | /// A string used for case-sensitive comparisons against the end of type names. 42 | /// The description to set on components found by this rule. 43 | /// The technology name(s) to set on components found by this rule. 44 | public NameSuffixTypeMatcher(string suffix, string description, string technology) 45 | { 46 | this.Suffix = suffix; 47 | this.Description = description; 48 | this.Technology = technology; 49 | } 50 | 51 | /// 52 | /// 53 | /// 54 | /// The rule implemented by identifies components from types whose names 55 | /// end with the case-sensitive string in . All types in the assembly whose names end with 56 | /// that suffix will be identified as a component and added to the model. 57 | /// 58 | /// 59 | public bool Matches(TypeDefinition type) 60 | { 61 | return type.Name.EndsWith(this.Suffix); 62 | } 63 | 64 | /// 65 | public string GetDescription() 66 | { 67 | return this.Description; 68 | } 69 | 70 | /// 71 | public string GetTechnology() 72 | { 73 | return this.Technology; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/ExtendsClassTypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | using Structurizr.Cecil; 4 | 5 | namespace Structurizr.Analysis 6 | { 7 | /// 8 | /// Implements a component identification rule based on inheritance from a class. 9 | /// 10 | /// 11 | /// 12 | /// The ExtendsClassTypeMatcher class provides component identification by looking for types which are subtypes of 13 | /// the class represented by the property. 14 | /// 15 | /// 16 | public class ExtendsClassTypeMatcher : ITypeMatcher 17 | { 18 | /// 19 | /// Gets the type information of the base class used for identifying components for this rule. 20 | /// 21 | /// The object representing the class used by the rule. 22 | public TypeDefinition ClassType { get; private set; } 23 | 24 | /// 25 | /// Gets the description to use for components matching the rule. 26 | /// 27 | /// A string containing the Description property for matching components. 28 | /// 29 | /// 30 | public string Description { get; private set; } 31 | 32 | /// 33 | /// Gets the technology name(s) to use for components matching the rule. 34 | /// 35 | /// A string containing the Technology property for matching components. 36 | /// 37 | /// 38 | public string Technology { get; private set; } 39 | 40 | /// 41 | /// Creates a new instance of based on the provided class. 42 | /// 43 | /// 44 | /// The object representing the base class that will be used to identify components. 45 | /// 46 | /// The description to set on components found by this rule. 47 | /// The technology name(s) to set on components found by this rule. 48 | public ExtendsClassTypeMatcher(TypeDefinition classType, string description, string technology) 49 | { 50 | this.ClassType = classType; 51 | this.Description = description; 52 | this.Technology = technology; 53 | } 54 | 55 | /// 56 | /// 57 | /// 58 | /// The rule implemented by identifies components from types that 59 | /// inherit from the class represented by . All types in the assembly under analysis 60 | /// which have that class as a base type at any depth of inheritance will be identified as a component and 61 | /// added to the model. 62 | /// 63 | /// 64 | public bool Matches(TypeDefinition type) 65 | { 66 | return type.IsSubclassOf(this.ClassType); 67 | } 68 | 69 | /// 70 | public string GetDescription() 71 | { 72 | return this.Description; 73 | } 74 | 75 | /// 76 | public string GetTechnology() 77 | { 78 | return this.Technology; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/NameSuffixTypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Implements a component identification rule based on matching a type name against a case-sensitive suffix. 7 | /// 8 | /// 9 | /// 10 | /// The NameSuffixTypeMatcher class provides component identification by looking for types whose names end with the 11 | /// value of the property. The name comparison is case-sensitive. 12 | /// 13 | /// 14 | public class NameSuffixTypeMatcher : ITypeMatcher 15 | { 16 | 17 | /// 18 | /// Gets the string used for suffix comparison for identifying components with this rule. 19 | /// 20 | /// A string used for case-sensitive comparisons against types used by the rule. 21 | public string Suffix { get; private set; } 22 | 23 | /// 24 | /// Gets the description to use for components matching the rule. 25 | /// 26 | /// A string containing the Description property for matching components. 27 | /// 28 | /// 29 | public string Description { get; private set; } 30 | 31 | /// 32 | /// Gets the technology name(s) to use for components matching the rule. 33 | /// 34 | /// A string containing the Technology property for matching components. 35 | /// 36 | /// 37 | public string Technology { get; private set; } 38 | 39 | /// 40 | /// Creates a new instance of using the provided suffix string. 41 | /// 42 | /// A string used for case-sensitive comparisons against the end of type names. 43 | /// The description to set on components found by this rule. 44 | /// The technology name(s) to set on components found by this rule. 45 | public NameSuffixTypeMatcher(string suffix, string description, string technology) 46 | { 47 | this.Suffix = suffix; 48 | this.Description = description; 49 | this.Technology = technology; 50 | 51 | if (Suffix == null || Suffix.Trim().Length == 0) 52 | { 53 | throw new ArgumentException("A suffix must be supplied"); 54 | } 55 | } 56 | 57 | /// 58 | /// 59 | /// 60 | /// The rule implemented by identifies components from types whose names 61 | /// end with the case-sensitive string in . All types in the assembly whose names end with 62 | /// that suffix will be identified as a component and added to the model. 63 | /// 64 | /// 65 | public bool Matches(Type type) 66 | { 67 | return type.Name.EndsWith(Suffix); 68 | } 69 | 70 | /// 71 | public string GetDescription() 72 | { 73 | return this.Description; 74 | } 75 | 76 | /// 77 | public string GetTechnology() 78 | { 79 | return this.Technology; 80 | } 81 | 82 | } 83 | } -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/ExtendsClassMatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Implements a component identification rule based on inheritance from a class. 7 | /// 8 | /// 9 | /// 10 | /// The ExtendsClassTypeMatcher class provides component identification by looking for types which are subtypes of 11 | /// the class represented by the property. 12 | /// 13 | /// 14 | public class ExtendsClassTypeMatcher : ITypeMatcher 15 | { 16 | 17 | /// 18 | /// Gets the type information of the base class used for identifying components for this rule. 19 | /// 20 | /// The object representing the class used by the rule. 21 | public Type ClassType { get; private set; } 22 | 23 | /// 24 | /// Gets the description to use for components matching the rule. 25 | /// 26 | /// A string containing the Description property for matching components. 27 | /// 28 | /// 29 | public string Description { get; private set; } 30 | 31 | /// 32 | /// Gets the technology name(s) to use for components matching the rule. 33 | /// 34 | /// A string containing the Technology property for matching components. 35 | /// 36 | /// 37 | public string Technology { get; private set; } 38 | 39 | /// 40 | /// Creates a new instance of based on the provided class. 41 | /// 42 | /// 43 | /// The object representing the base class that will be used to identify components. 44 | /// 45 | /// The description to set on components found by this rule. 46 | /// The technology name(s) to set on components found by this rule. 47 | public ExtendsClassTypeMatcher(Type classType, string description, string technology) 48 | { 49 | this.ClassType = classType; 50 | this.Description = description; 51 | this.Technology = technology; 52 | 53 | if (!ClassType.IsClass) 54 | { 55 | throw new ArgumentException(ClassType.FullName + " is not a class type"); 56 | } 57 | } 58 | 59 | /// 60 | /// 61 | /// 62 | /// The rule implemented by identifies components from types that 63 | /// inherit from the class represented by . All types in the assembly under analysis 64 | /// which have that class as a base type at any depth of inheritance will be identified as a component and 65 | /// added to the model. 66 | /// 67 | /// 68 | public bool Matches(Type type) 69 | { 70 | return type.IsSubclassOf(ClassType); 71 | } 72 | 73 | /// 74 | public string GetDescription() 75 | { 76 | return this.Description; 77 | } 78 | 79 | /// 80 | public string GetTechnology() 81 | { 82 | return this.Technology; 83 | } 84 | 85 | } 86 | } -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/InterfaceImplementationTypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | 3 | using Structurizr.Cecil; 4 | 5 | namespace Structurizr.Analysis 6 | { 7 | /// 8 | /// Implements a component identification rule based on implementation of an interface. 9 | /// 10 | /// 11 | /// 12 | /// The InterfaceImplementationTypeMatcher class provides component identification by looking for types which 13 | /// implement the interface represented by the property. 14 | /// 15 | /// 16 | public class InterfaceImplementationTypeMatcher : ITypeMatcher 17 | { 18 | /// 19 | /// Gets the type information of the interface used for identifying components for this rule. 20 | /// 21 | /// The object representing the interface used by the rule. 22 | public TypeDefinition InterfaceType { get; private set; } 23 | 24 | /// 25 | /// Gets the description to use for components matching the rule. 26 | /// 27 | /// A string containing the Description property for matching components. 28 | /// 29 | /// 30 | public string Description { get; private set; } 31 | 32 | /// 33 | /// Gets the technology name(s) to use for components matching the rule. 34 | /// 35 | /// A string containing the Technology property for matching components. 36 | /// 37 | /// 38 | public string Technology { get; private set; } 39 | 40 | /// 41 | /// Creates a new instance of based on the provided interface. 42 | /// 43 | /// 44 | /// The object representing the interface that will be used to identify components. 45 | /// 46 | /// The description to set on components found by this rule. 47 | /// The technology name(s) to set on components found by this rule. 48 | public InterfaceImplementationTypeMatcher(TypeDefinition interfaceType, 49 | string description, 50 | string technology) 51 | { 52 | this.InterfaceType = interfaceType; 53 | this.Description = description; 54 | this.Technology = technology; 55 | } 56 | 57 | /// 58 | /// 59 | /// 60 | /// The rule implemented by identifies components from types 61 | /// that implement the interface represented by . All types in the assembly under 62 | /// analysis which implement the interface at any depth of inheritance will be identified as a component and 63 | /// added to the model. 64 | /// 65 | /// 66 | public bool Matches(TypeDefinition type) 67 | { 68 | return this.InterfaceType.IsAssignableFrom(type); 69 | } 70 | 71 | /// 72 | public string GetDescription() 73 | { 74 | return this.Description; 75 | } 76 | 77 | /// 78 | public string GetTechnology() 79 | { 80 | return this.Technology; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/InterfaceImplementationTypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizr.Analysis 4 | { 5 | /// 6 | /// Implements a component identification rule based on implementation of an interface. 7 | /// 8 | /// 9 | /// 10 | /// The InterfaceImplementationTypeMatcher class provides component identification by looking for types which 11 | /// implement the interface represented by the property. 12 | /// 13 | /// 14 | public class InterfaceImplementationTypeMatcher : ITypeMatcher 15 | { 16 | 17 | /// 18 | /// Gets the type information of the interface used for identifying components for this rule. 19 | /// 20 | /// The object representing the interface used by the rule. 21 | public Type InterfaceType { get; private set; } 22 | 23 | /// 24 | /// Gets the description to use for components matching the rule. 25 | /// 26 | /// A string containing the Description property for matching components. 27 | /// 28 | /// 29 | public string Description { get; private set; } 30 | 31 | /// 32 | /// Gets the technology name(s) to use for components matching the rule. 33 | /// 34 | /// A string containing the Technology property for matching components. 35 | /// 36 | /// 37 | public string Technology { get; private set; } 38 | 39 | /// 40 | /// Creates a new instance of based on the provided interface. 41 | /// 42 | /// 43 | /// The object representing the interface that will be used to identify components. 44 | /// 45 | /// The description to set on components found by this rule. 46 | /// The technology name(s) to set on components found by this rule. 47 | public InterfaceImplementationTypeMatcher(Type interfaceType, string description, string technology) 48 | { 49 | this.InterfaceType = interfaceType; 50 | this.Description = description; 51 | this.Technology = technology; 52 | 53 | if (!InterfaceType.IsInterface) 54 | { 55 | throw new ArgumentException(InterfaceType.FullName + " is not an interface type"); 56 | } 57 | } 58 | 59 | /// 60 | /// 61 | /// 62 | /// The rule implemented by identifies components from types 63 | /// that implement the interface represented by . All types in the assembly under 64 | /// analysis which implement the interface at any depth of inheritance will be identified as a component and 65 | /// added to the model. 66 | /// 67 | /// 68 | public bool Matches(Type type) 69 | { 70 | return InterfaceType.IsAssignableFrom(type); 71 | } 72 | 73 | /// 74 | public string GetDescription() 75 | { 76 | return this.Description; 77 | } 78 | 79 | /// 80 | public string GetTechnology() 81 | { 82 | return this.Technology; 83 | } 84 | 85 | } 86 | } -------------------------------------------------------------------------------- /Structurizr.Cecil.Examples/StructurizrAnnotations.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | using Mono.Cecil; 4 | 5 | using Structurizr.Analysis; 6 | using Structurizr.Api; 7 | 8 | namespace Structurizr.Examples 9 | { 10 | 11 | /// 12 | /// A small example that illustrates how to use the Structurizr annotations 13 | /// in conjunction with the StructurizrAnnotationsComponentFinderStrategy. 14 | /// 15 | /// The live workspace is available to view at https://structurizr.com/share/38339 16 | /// 17 | public class StructurizrAnnotations 18 | { 19 | private const string DatabaseTag = "Database"; 20 | 21 | private const long WorkspaceId = 38339; 22 | private const string ApiKey = "key"; 23 | private const string ApiSecret = "secret"; 24 | 25 | static void Main() 26 | { 27 | Workspace workspace = new Workspace("Structurizr for .NET Annotations", "This is a model of my software system."); 28 | Model model = workspace.Model; 29 | 30 | Person user = model.AddPerson("User", "A user of my software system."); 31 | SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); 32 | 33 | Container webApplication = softwareSystem.AddContainer("Web Application", "Provides users with information.", "C#"); 34 | Container database = softwareSystem.AddContainer("Database", "Stores information.", "Relational database schema"); 35 | database.AddTags(DatabaseTag); 36 | 37 | string assemblyPath = typeof(StructurizrAnnotations).Assembly.Location; 38 | DefaultAssemblyResolver resolver = new DefaultAssemblyResolver(); 39 | resolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath)); 40 | AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly( 41 | assemblyPath, 42 | new ReaderParameters { AssemblyResolver = resolver } 43 | ); 44 | 45 | ComponentFinder componentFinder = new ComponentFinder( 46 | webApplication, 47 | "Structurizr.Examples.Annotations", 48 | new StructurizrAnnotationsComponentFinderStrategy(assembly) 49 | ); 50 | componentFinder.FindComponents(); 51 | model.AddImplicitRelationships(); 52 | 53 | ViewSet views = workspace.Views; 54 | SystemContextView contextView = views.CreateSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); 55 | contextView.AddAllElements(); 56 | 57 | ContainerView containerView = views.CreateContainerView(softwareSystem, "Containers", "The container diagram from my software system."); 58 | containerView.AddAllElements(); 59 | 60 | ComponentView componentView = views.CreateComponentView(webApplication, "Components", "The component diagram for the web application."); 61 | componentView.AddAllElements(); 62 | 63 | Styles styles = views.Configuration.Styles; 64 | styles.Add(new ElementStyle(Tags.Element) { Color = "#ffffff" }); 65 | styles.Add(new ElementStyle(Tags.SoftwareSystem) { Background = "#1168bd" }); 66 | styles.Add(new ElementStyle(Tags.Container) { Background = "#438dd5" }); 67 | styles.Add(new ElementStyle(Tags.Component) { Background = "#85bbf0", Color = "#000000" }); 68 | styles.Add(new ElementStyle(Tags.Person) { Background = "#08427b", Shape = Shape.Person }); 69 | styles.Add(new ElementStyle(DatabaseTag) { Shape = Shape.Cylinder }); 70 | 71 | StructurizrClient structurizrClient = new StructurizrClient(ApiKey, ApiSecret); 72 | structurizrClient.PutWorkspace(WorkspaceId, workspace); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/CustomAttributeTypeMatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | using Mono.Cecil; 4 | 5 | namespace Structurizr.Analysis 6 | { 7 | /// 8 | /// Implements a component identification rule based on the use of custom attributes. 9 | /// 10 | /// 11 | /// 12 | /// The CustomAttributeTypeMatcher class provides component identification by looking for types decorated with the 13 | /// attribute represented by the property. This can be any attribute class from the 14 | /// assembly under analysis or any of its dependencies. 15 | /// 16 | /// 17 | public class CustomAttributeTypeMatcher : ITypeMatcher 18 | { 19 | /// 20 | /// Gets the type information of the attribute type used for identifying components for this rule. 21 | /// 22 | /// The object representing the attribute type used by the rule. 23 | public TypeDefinition AttributeType { get; private set; } 24 | 25 | /// 26 | /// Gets the description to use for components matching the rule. 27 | /// 28 | /// A string containing the Description property for matching components. 29 | /// 30 | /// 31 | public string Description { get; private set; } 32 | 33 | /// 34 | /// Gets the technology name(s) to use for components matching the rule. 35 | /// 36 | /// A string containing the Technology property for matching components. 37 | /// 38 | /// 39 | public string Technology { get; private set; } 40 | 41 | /// 42 | /// Creates a new instance of based on the provided attribute type. 43 | /// 44 | /// 45 | /// The object representing the attribute type that will be used to identify components. 46 | /// 47 | /// The description to set on components found by this rule. 48 | /// The technology name(s) to set on components found by this rule. 49 | public CustomAttributeTypeMatcher(TypeDefinition attributeType, 50 | string description, 51 | string technology) 52 | { 53 | this.AttributeType = attributeType; 54 | this.Description = description; 55 | this.Technology = technology; 56 | } 57 | 58 | /// 59 | /// 60 | /// 61 | /// The rule implemented by identifies components from types that are 62 | /// decorated with the attribute class represented by . All types in the assembly 63 | /// under analysis that have this attribute on the type itself (not on any of its members) will be identified 64 | /// as a component by the rule and added to the model. 65 | /// 66 | /// 67 | public bool Matches(TypeDefinition type) 68 | { 69 | return type.CustomAttributes.Any(ca => ca.AttributeType.Resolve().MetadataToken == this.AttributeType.MetadataToken); 70 | } 71 | 72 | /// 73 | public string GetDescription() 74 | { 75 | return this.Description; 76 | } 77 | 78 | /// 79 | public string GetTechnology() 80 | { 81 | return this.Technology; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/SupportingTypes/ReferencedTypesSupportingTypesStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Structurizr.Analysis 6 | { 7 | 8 | /// 9 | /// Implements a which finds all types referenced by the primary code element 10 | /// of a , and optionally those referenced by its secondary code elements. 11 | /// 12 | public class ReferencedTypesSupportingTypesStrategy : SupportingTypesStrategy 13 | { 14 | 15 | private bool _includeIndirectlyReferencedTypes; 16 | 17 | /// 18 | /// Creates a new instance of which includes the option to 19 | /// find supporting types from secondary code elements. 20 | /// 21 | public ReferencedTypesSupportingTypesStrategy() : this(true) 22 | { 23 | } 24 | 25 | /// 26 | /// Creates a new instance of which allows the option to 27 | /// find supporting types from secondary code elements to be expressly enabled or disabled. 28 | /// 29 | /// 30 | /// Whether or not to include supporting types identified from secondary code elements of the component. 31 | /// 32 | /// 33 | /// 34 | /// Setting to is equivalent to using 35 | /// the default constructor for , and will find and add to 36 | /// a component's supporting types any types referenced by the secondary code elements of the component, in 37 | /// addition to those types referenced by the primary code element of the component. Setting the param to 38 | /// instead will only find and add those types which are referenced directly by the 39 | /// primary code element of the component. 40 | /// 41 | /// 42 | /// Only those types that are in the same namespace as the code elements of a component will actually be added 43 | /// as supporting types of the component. 44 | /// 45 | /// 46 | public ReferencedTypesSupportingTypesStrategy(bool includeIndirectlyReferencedTypes) 47 | { 48 | _includeIndirectlyReferencedTypes = includeIndirectlyReferencedTypes; 49 | } 50 | 51 | /// 52 | public override HashSet FindSupportingTypes(Component component) 53 | { 54 | HashSet referencedTypes = new HashSet(); 55 | referencedTypes.UnionWith(GetReferencedTypesInNamespace(component.Type)); 56 | 57 | foreach (CodeElement codeElement in component.CodeElements) 58 | { 59 | referencedTypes.UnionWith(GetReferencedTypesInNamespace(codeElement.Type)); 60 | } 61 | 62 | if (_includeIndirectlyReferencedTypes) { 63 | int numberOfTypes = referencedTypes.Count; 64 | bool foundMore = true; 65 | while (foundMore) { 66 | HashSet indirectlyReferencedTypes = new HashSet(); 67 | foreach (string type in referencedTypes) 68 | { 69 | indirectlyReferencedTypes.UnionWith(GetReferencedTypesInNamespace(type)); 70 | } 71 | referencedTypes.UnionWith(indirectlyReferencedTypes); 72 | 73 | if (referencedTypes.Count > numberOfTypes) 74 | { 75 | foundMore = true; 76 | numberOfTypes = referencedTypes.Count; 77 | } 78 | else 79 | { 80 | foundMore = false; 81 | } 82 | } 83 | } 84 | 85 | return referencedTypes; 86 | } 87 | 88 | private IEnumerable GetReferencedTypesInNamespace(string typeName) 89 | { 90 | IEnumerable referencedTypes = TypeRepository.GetReferencedTypes(typeName); 91 | return referencedTypes.Where(t => t.StartsWith(TypeRepository.Namespace)); 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/SupportingTypes/ReferencedTypesSupportingTypesStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Structurizr.Analysis.SupportingTypes 5 | { 6 | /// 7 | /// Implements a which finds all types referenced by the primary code element 8 | /// of a , and optionally those referenced by its secondary code elements. 9 | /// 10 | public class ReferencedTypesSupportingTypesStrategy : SupportingTypesStrategy 11 | { 12 | private bool _includeIndirectlyReferencedTypes; 13 | 14 | /// 15 | /// Creates a new instance of which includes the option to 16 | /// find supporting types from secondary code elements. 17 | /// 18 | public ReferencedTypesSupportingTypesStrategy() : this(true) 19 | { 20 | } 21 | 22 | /// 23 | /// Creates a new instance of which allows the option to 24 | /// find supporting types from secondary code elements to be expressly enabled or disabled. 25 | /// 26 | /// 27 | /// Whether or not to include supporting types identified from secondary code elements of the component. 28 | /// 29 | /// 30 | /// 31 | /// Setting to is equivalent to using 32 | /// the default constructor for , and will find and add to 33 | /// a component's supporting types any types referenced by the secondary code elements of the component, in 34 | /// addition to those types referenced by the primary code element of the component. Setting the param to 35 | /// instead will only find and add those types which are referenced directly by the 36 | /// primary code element of the component. 37 | /// 38 | /// 39 | /// Only those types that are in the same namespace as the code elements of a component will actually be added 40 | /// as supporting types of the component. 41 | /// 42 | /// 43 | public ReferencedTypesSupportingTypesStrategy(bool includeIndirectlyReferencedTypes) 44 | { 45 | _includeIndirectlyReferencedTypes = includeIndirectlyReferencedTypes; 46 | } 47 | 48 | /// 49 | public override HashSet FindSupportingTypes(Component component) 50 | { 51 | HashSet referencedTypes = new HashSet(); 52 | referencedTypes.UnionWith(GetReferencedTypesInNamespace(component.Type)); 53 | 54 | foreach (CodeElement codeElement in component.CodeElements) 55 | { 56 | referencedTypes.UnionWith(GetReferencedTypesInNamespace(codeElement.Type)); 57 | } 58 | 59 | if (_includeIndirectlyReferencedTypes) 60 | { 61 | int numberOfTypes = referencedTypes.Count; 62 | bool foundMore = true; 63 | while (foundMore) 64 | { 65 | HashSet indirectlyReferencedTypes = new HashSet(); 66 | foreach (string type in referencedTypes) 67 | { 68 | indirectlyReferencedTypes.UnionWith(GetReferencedTypesInNamespace(type)); 69 | } 70 | referencedTypes.UnionWith(indirectlyReferencedTypes); 71 | 72 | if (referencedTypes.Count > numberOfTypes) 73 | { 74 | foundMore = true; 75 | numberOfTypes = referencedTypes.Count; 76 | } 77 | else 78 | { 79 | foundMore = false; 80 | } 81 | } 82 | } 83 | 84 | return referencedTypes; 85 | } 86 | 87 | private IEnumerable GetReferencedTypesInNamespace(string typeName) 88 | { 89 | IEnumerable referencedTypes = TypeRepository.GetReferencedTypes(typeName); 90 | return referencedTypes.Where(t => t.StartsWith(TypeRepository.Namespace)); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Structurizr.Analysis/Analysis/ComponentFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Structurizr.Analysis 6 | { 7 | 8 | /// 9 | /// This class allows you to find components in a .NET codebase, when used in conjunction 10 | /// with a number of pluggable component finder strategies. 11 | /// 12 | public class ComponentFinder 13 | { 14 | 15 | /// 16 | /// The Container that components will be added to. 17 | /// 18 | public Container Container { get; } 19 | 20 | /// 21 | /// The name of the namespace to be scanned. 22 | /// 23 | public string Namespace { get; } 24 | 25 | /// 26 | /// The set of regexes that define which types should be excluded during the component finding process. 27 | /// 28 | public HashSet Exclusions { get; } 29 | 30 | private readonly List _componentFinderStrategies = new List(); 31 | 32 | /// 33 | /// Create a new component finder. 34 | /// 35 | /// The Container that components will be added to 36 | /// The .NET namespace name to be scanned (e.g. "MyCompany.MyApp") 37 | /// One or more ComponentFinderStrategy objects, describing how to find components 38 | public ComponentFinder(Container container, string namespaceToScan, params ComponentFinderStrategy[] componentFinderStrategies) 39 | { 40 | if (container == null) 41 | { 42 | throw new ArgumentException("A container must be specified."); 43 | } 44 | 45 | if (namespaceToScan == null || namespaceToScan.Trim().Length == 0) 46 | { 47 | throw new ArgumentException("A package name must be specified."); 48 | } 49 | 50 | if (componentFinderStrategies.Length == 0) 51 | { 52 | throw new ArgumentException("One or more ComponentFinderStrategy objects must be specified."); 53 | } 54 | 55 | Container = container; 56 | Namespace = namespaceToScan; 57 | 58 | Exclusions = new HashSet(); 59 | Exclusions.Add(new Regex(@"System\.")); 60 | 61 | foreach (ComponentFinderStrategy componentFinderStrategy in componentFinderStrategies) 62 | { 63 | _componentFinderStrategies.Add(componentFinderStrategy); 64 | componentFinderStrategy.ComponentFinder = this; 65 | } 66 | } 67 | 68 | /// 69 | /// Find components, using all of the configured component finder strategies 70 | /// in the order they were added. 71 | /// 72 | /// The set of components that were found. 73 | public ICollection FindComponents() 74 | { 75 | List componentsFound = new List(); 76 | 77 | foreach (ComponentFinderStrategy componentFinderStrategy in _componentFinderStrategies) 78 | { 79 | componentFinderStrategy.BeforeFindComponents(); 80 | } 81 | 82 | foreach (ComponentFinderStrategy componentFinderStrategy in _componentFinderStrategies) { 83 | componentsFound.AddRange(componentFinderStrategy.FindComponents()); 84 | } 85 | 86 | foreach (ComponentFinderStrategy componentFinderStrategy in _componentFinderStrategies) 87 | { 88 | componentFinderStrategy.AfterFindComponents(); 89 | } 90 | 91 | return componentsFound; 92 | } 93 | 94 | /// 95 | /// Adds one or more regexes to the set of regexes that define which types should be excluded during the component finding process. 96 | /// 97 | /// One or more regular expressions, as strings. 98 | public void Exclude(params string[] regexes) 99 | { 100 | foreach (string regex in regexes) 101 | { 102 | Exclusions.Add(new Regex(regex)); 103 | } 104 | } 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /Structurizr.Cecil/Util/TypeDefinitionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | 5 | using Mono.Cecil; 6 | 7 | namespace Structurizr.Cecil 8 | { 9 | /// 10 | /// Provides extension methods for . 11 | /// 12 | /// 13 | /// Sourced from https://stackoverflow.com/questions/40018991/how-to-implement-isassignablefrom-with-mono-cecil 14 | /// 15 | public static class TypeDefinitionExtensions 16 | { 17 | /// 18 | /// Is childTypeDef a subclass of parentTypeDef. Does not test interface inheritance 19 | /// 20 | /// 21 | /// 22 | /// 23 | public static bool IsSubclassOf(this TypeDefinition childTypeDef, TypeDefinition parentTypeDef) => 24 | (childTypeDef.MetadataToken != parentTypeDef.MetadataToken || childTypeDef.Module.Mvid != parentTypeDef.Module.Mvid) 25 | && childTypeDef 26 | .EnumerateBaseClasses() 27 | .Any(b => b.MetadataToken == parentTypeDef.MetadataToken && b.Module.Mvid == parentTypeDef.Module.Mvid); 28 | 29 | /// 30 | /// Does childType inherit from parentInterface 31 | /// 32 | /// 33 | /// 34 | /// 35 | public static bool DoesAnySubTypeImplementInterface(this TypeDefinition childType, TypeDefinition parentInterfaceDef) 36 | { 37 | Debug.Assert(parentInterfaceDef.IsInterface); 38 | return childType 39 | .EnumerateBaseClasses() 40 | .Any(typeDefinition => typeDefinition.DoesSpecificTypeImplementInterface(parentInterfaceDef)); 41 | } 42 | 43 | /// 44 | /// Does the childType directly inherit from parentInterface. Base 45 | /// classes of childType are not tested 46 | /// 47 | /// 48 | /// 49 | /// 50 | public static bool DoesSpecificTypeImplementInterface(this TypeDefinition childTypeDef, TypeDefinition parentInterfaceDef) 51 | { 52 | Debug.Assert(parentInterfaceDef.IsInterface); 53 | return childTypeDef 54 | .Interfaces 55 | .Any(ifaceDef => DoesSpecificInterfaceImplementInterface(ifaceDef.InterfaceType.Resolve(), parentInterfaceDef)); 56 | } 57 | 58 | /// 59 | /// Does interface iface0 equal or implement interface iface1 60 | /// 61 | /// 62 | /// 63 | /// 64 | public static bool DoesSpecificInterfaceImplementInterface(TypeDefinition iface0, TypeDefinition iface1) 65 | { 66 | Debug.Assert(iface1.IsInterface); 67 | Debug.Assert(iface0.IsInterface); 68 | return (iface0.MetadataToken == iface1.MetadataToken && iface0.Module.Mvid == iface1.Module.Mvid) 69 | || iface0.DoesAnySubTypeImplementInterface(iface1); 70 | } 71 | 72 | /// 73 | /// Is source type assignable to target type 74 | /// 75 | /// 76 | /// 77 | /// 78 | public static bool IsAssignableFrom(this TypeDefinition target, TypeDefinition source) 79 | => target == source 80 | || (target.MetadataToken == source.MetadataToken && target.Module.Mvid == source.Module.Mvid) 81 | || source.IsSubclassOf(target) 82 | || target.IsInterface && source.DoesAnySubTypeImplementInterface(target); 83 | 84 | /// 85 | /// Enumerate the current type, it's parent and all the way to the top type 86 | /// 87 | /// 88 | /// 89 | public static IEnumerable EnumerateBaseClasses(this TypeDefinition klassType) 90 | { 91 | for (var typeDefinition = klassType; typeDefinition != null; typeDefinition = typeDefinition.BaseType?.Resolve()) 92 | { 93 | yield return typeDefinition; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Structurizr.Roslyn/Analysis/TypeSummaryComponentFinderStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.CodeAnalysis.MSBuild; 4 | using Microsoft.CodeAnalysis; 5 | using System.Linq; 6 | using System.Xml.Linq; 7 | 8 | namespace Structurizr.Analysis 9 | { 10 | 11 | /// 12 | /// This is a component finder that enriches existing components with the type level summary 13 | /// comment (i.e. this comment). It uses Roslyn and needs access to the source code. 14 | /// 15 | public class TypeSummaryComponentFinderStrategy : ComponentFinderStrategy 16 | { 17 | 18 | private ComponentFinder _componentFinder; 19 | /// 20 | public ComponentFinder ComponentFinder 21 | { 22 | get { return _componentFinder; } 23 | set 24 | { 25 | _componentFinder = value; 26 | } 27 | } 28 | 29 | private HashSet _components = new HashSet(); 30 | 31 | /// 32 | /// Gets or sets the path to the solution (.sln) file containing the project to analyze. 33 | /// 34 | /// The file path of the Visual Studio solution file to use. 35 | public string PathToSolution { get; set; } 36 | 37 | /// 38 | /// Gets or sets the name of the project within the solution to analyze. 39 | /// 40 | /// The name of the project to analyze. 41 | public string ProjectName { get; set; } 42 | 43 | /// 44 | /// Creates a new instance of for a specified project within a 45 | /// specified Visual Studio solution. 46 | /// 47 | /// 48 | /// 49 | public TypeSummaryComponentFinderStrategy(string pathToSolution, string projectName) 50 | { 51 | this.PathToSolution = pathToSolution; 52 | this.ProjectName = projectName; 53 | } 54 | 55 | /// 56 | public void BeforeFindComponents() 57 | { 58 | } 59 | 60 | /// 61 | public IEnumerable FindComponents() 62 | { 63 | MSBuildWorkspace msWorkspace = MSBuildWorkspace.Create(); 64 | 65 | Solution solution = msWorkspace.OpenSolutionAsync(PathToSolution).Result; 66 | Project project = solution.Projects.Where(p => p.Name == ProjectName).First(); 67 | Compilation compilation = project.GetCompilationAsync().Result; 68 | foreach (Component component in ComponentFinder.Container.Components) 69 | { 70 | foreach (CodeElement codeElement in component.CodeElements) 71 | { 72 | try 73 | { 74 | string type = codeElement.Type.Substring(0, component.Type.IndexOf(',')); // remove the assembly name from the type 75 | INamedTypeSymbol symbol = compilation.GetTypeByMetadataName(type); 76 | foreach (Microsoft.CodeAnalysis.Location location in symbol.Locations) 77 | { 78 | if (location.IsInSource) 79 | { 80 | codeElement.Url = new Uri(location.SourceTree.FilePath).AbsoluteUri; 81 | codeElement.Size += location.SourceTree.GetText().Lines.Count; 82 | component.Size += codeElement.Size; 83 | } 84 | } 85 | 86 | if (codeElement.Role == CodeElementRole.Primary) 87 | { 88 | string xml = symbol.GetDocumentationCommentXml(); 89 | if (xml != null && xml.Trim().Length > 0) 90 | { 91 | XDocument xdoc = XDocument.Parse(xml); 92 | string comment = xdoc.Descendants("summary").First().Value.Trim(); 93 | component.Description = comment; 94 | } 95 | } 96 | } 97 | catch (Exception e) 98 | { 99 | Console.WriteLine("Could not get summary comment for " + component.Name + ": " + e.Message); 100 | } 101 | } 102 | } 103 | 104 | return new HashSet(); 105 | } 106 | 107 | /// 108 | public void AfterFindComponents() 109 | { 110 | } 111 | 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | TestResults 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | # NuGet v3's project.json files produces more ignoreable files 155 | *.nuget.props 156 | *.nuget.targets 157 | 158 | # Microsoft Azure Build Output 159 | csx/ 160 | *.build.csdef 161 | 162 | # Microsoft Azure Emulator 163 | ecf/ 164 | rcf/ 165 | 166 | # Microsoft Azure ApplicationInsights config file 167 | ApplicationInsights.config 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | ~$* 182 | *~ 183 | *.dbmdl 184 | *.dbproj.schemaview 185 | *.pfx 186 | *.publishsettings 187 | node_modules/ 188 | orleans.codegen.cs 189 | 190 | # RIA/Silverlight projects 191 | Generated_Code/ 192 | 193 | # Backup & report files from converting an old project file 194 | # to a newer Visual Studio version. Backup files are not needed, 195 | # because we have git ;-) 196 | _UpgradeReport_Files/ 197 | Backup*/ 198 | UpgradeLog*.XML 199 | UpgradeLog*.htm 200 | 201 | # SQL Server files 202 | *.mdf 203 | *.ldf 204 | 205 | # Business Intelligence projects 206 | *.rdl.data 207 | *.bim.layout 208 | *.bim_*.settings 209 | 210 | # Microsoft Fakes 211 | FakesAssemblies/ 212 | 213 | # GhostDoc plugin setting file 214 | *.GhostDoc.xml 215 | 216 | # Node.js Tools for Visual Studio 217 | .ntvs_analysis.dat 218 | 219 | # Visual Studio 6 build log 220 | *.plg 221 | 222 | # Visual Studio 6 workspace options file 223 | *.opt 224 | 225 | # Visual Studio LightSwitch build output 226 | **/*.HTMLClient/GeneratedArtifacts 227 | **/*.DesktopClient/GeneratedArtifacts 228 | **/*.DesktopClient/ModelManifest.xml 229 | **/*.Server/GeneratedArtifacts 230 | **/*.Server/ModelManifest.xml 231 | _Pvt_Extensions 232 | 233 | # Paket dependency manager 234 | .paket/paket.exe 235 | 236 | # FAKE - F# Make 237 | .fake/ 238 | 239 | # Rider project files 240 | .idea 241 | 242 | # Structurizr JSON files 243 | Structurizr.Examples/*.json 244 | -------------------------------------------------------------------------------- /Structurizr.AdrTools.Tests/AdrTools/AdrToolsImporterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using Structurizr.Documentation; 5 | using Xunit; 6 | 7 | namespace Structurizr.AdrTools.Tests 8 | { 9 | 10 | public class AdrToolsImporterTests 11 | { 12 | 13 | private Workspace _workspace; 14 | private SoftwareSystem _softwareSystem; 15 | private Documentation.Documentation _documentation; 16 | 17 | public AdrToolsImporterTests() 18 | { 19 | _workspace = new Workspace("Name", "Description"); 20 | _softwareSystem = _workspace.Model.AddSoftwareSystem("Software System", "Description"); 21 | _documentation = _workspace.Documentation; 22 | } 23 | 24 | [Fact] 25 | public void Test_Construction_ThrowsAnException_WhenAWorkspaceIsNotSpecified() 26 | { 27 | try 28 | { 29 | new AdrToolsImporter(null, null); 30 | throw new TestFailedException(); 31 | } 32 | catch (ArgumentException iae) 33 | { 34 | Assert.Equal("A workspace must be specified.", iae.Message); 35 | } 36 | } 37 | 38 | [Fact] 39 | public void Test_Construction_ThrowsAnException_WhenADirectoryIsNotSpecified() 40 | { 41 | try 42 | { 43 | new AdrToolsImporter(_workspace, null); 44 | throw new TestFailedException(); 45 | } 46 | catch (ArgumentException iae) 47 | { 48 | Assert.Equal("The path to the architecture decision records must be specified.", iae.Message); 49 | } 50 | } 51 | 52 | [Fact] 53 | public void Test_Construction_ThrowsAnException_WhenADirectoryIsSpecifiedButItDoesNotExist() 54 | { 55 | try 56 | { 57 | new AdrToolsImporter(_workspace, new DirectoryInfo("some-random-path")); 58 | throw new TestFailedException(); 59 | } 60 | catch (ArgumentException iae) 61 | { 62 | Assert.True(iae.Message.EndsWith("some-random-path does not exist.")); 63 | } 64 | } 65 | 66 | [Fact] 67 | public void Test_ImportArchitectureDecisionRecords() 68 | { 69 | AdrToolsImporter importer = new AdrToolsImporter(_workspace, new DirectoryInfo("AdrTools\\adrs")); 70 | importer.ImportArchitectureDecisionRecords(); 71 | 72 | Assert.Equal(10, _documentation.Decisions.Count); 73 | 74 | Decision decision1 = _documentation.Decisions.Where(d => d.Id == "1").First(); 75 | Assert.Equal("1", decision1.Id); 76 | Assert.Equal("Record architecture decisions", decision1.Title); 77 | Assert.Equal("2016-02-12T00:00:00.0000000", decision1.Date.ToString("o")); 78 | Assert.Equal(DecisionStatus.Accepted, decision1.Status); 79 | Assert.Equal(Format.Markdown, decision1.Format); 80 | Assert.Equal("# 1. Record architecture decisions\n" + 81 | "\n" + 82 | "Date: 2016-02-12\n" + 83 | "\n" + 84 | "## Status\n" + 85 | "\n" + 86 | "Accepted\n" + 87 | "\n" + 88 | "## Context\n" + 89 | "\n" + 90 | "We need to record the architectural decisions made on this project.\n" + 91 | "\n" + 92 | "## Decision\n" + 93 | "\n" + 94 | "We will use Architecture Decision Records, as described by Michael Nygard in this article: http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions\n" + 95 | "\n" + 96 | "## Consequences\n" + 97 | "\n" + 98 | "See Michael Nygard's article, linked above.\n", 99 | decision1.Content); 100 | } 101 | 102 | [Fact] 103 | public void Test_ImportArchitectureDecisionRecords_RewritesLinksBetweenADRsWhenTheyAreNotAssociatedWithAnElement() 104 | { 105 | AdrToolsImporter importer = new AdrToolsImporter(_workspace, new DirectoryInfo("AdrTools\\adrs")); 106 | importer.ImportArchitectureDecisionRecords(); 107 | 108 | Decision decision5 = _documentation.Decisions.Where(d => d.Id == "5").First(); 109 | Assert.True(decision5.Content.Contains("Amended by [9. Help scripts](#/:9)")); 110 | } 111 | 112 | [Fact] 113 | public void Test_TmportArchitectureDecisionRecords_RewritesLinksBetweenADRsWhenTheyAreAssociatedWithAnElement() 114 | { 115 | AdrToolsImporter importer = new AdrToolsImporter(_workspace, new DirectoryInfo("AdrTools\\adrs")); 116 | importer.ImportArchitectureDecisionRecords(_softwareSystem); 117 | 118 | Decision decision5 = _documentation.Decisions.Where(d => d.Id == "5").First(); 119 | Assert.True(decision5.Content.Contains("Amended by [9. Help scripts](#%2FSoftware%20System:9)")); 120 | } 121 | 122 | [Fact] 123 | public void Test_ImportArchitectureDecisionRecords_SupportsTheIncorrectSpellingOfSuperseded() 124 | { 125 | AdrToolsImporter importer = new AdrToolsImporter(_workspace, new DirectoryInfo("AdrTools\\adrs")); 126 | importer.ImportArchitectureDecisionRecords(); 127 | 128 | Decision decision4 = _documentation.Decisions.Where(d => d.Id == "4").First(); 129 | Assert.Equal(DecisionStatus.Superseded, decision4.Status); 130 | Assert.True(decision4.Content.Contains("Superceded by [10. AsciiDoc format](#/:10)")); 131 | } 132 | 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /docs/c4-plantuml.md: -------------------------------------------------------------------------------- 1 | # C4-PlantUML 2 | 3 | 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: 4 | 5 | - Enterprise Context 6 | - System Context 7 | - Container 8 | - Component 9 | - Dynamic* 10 | - Deployment* 11 | 12 | *..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 13 | CustomBaseUrl=https://raw.githubusercontent.com/kirchsth/C4-PlantUML/master/ or if it is not set then the definition is merged in each diagram) 14 | 15 | 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): 16 | 17 | ```c# 18 | Workspace workspace = new Workspace("Getting Started", "This is a model of my software system."); 19 | Model model = workspace.Model; 20 | 21 | model.Enterprise = new Enterprise("Some Enterprise"); 22 | 23 | Person user = model.AddPerson("User", "A user of my software system."); 24 | SoftwareSystem softwareSystem = model.AddSoftwareSystem("Software System", "My software system."); 25 | var userUsesSystemRelation = user.Uses(softwareSystem, "Uses"); 26 | // a direction could be added to relation (active in all views) 27 | // userUsesSystemRelation.SetDirection(DirectionValues.Right); 28 | 29 | ViewSet views = workspace.Views; 30 | SystemContextView contextView = views.CreateSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); 31 | contextView.AddAllSoftwareSystems(); 32 | contextView.AddAllPeople(); 33 | 34 | // C4PlantUMLWriter support view specific directions too, e.g. "User" should be left of "Software System" only in this view 35 | contextView.Relationships 36 | .First(rv => rv.Relationship.SourceId == user.Id && rv.Relationship.DestinationId == softwareSystem.Id) 37 | .SetDirection(DirectionValues.Right); 38 | 39 | using (var stringWriter = new StringWriter()) 40 | { 41 | var plantUmlWriter = new C4PlantUmlWriter(); 42 | plantUmlWriter.Write(workspace, stringWriter); 43 | Console.WriteLine(stringWriter.ToString()); 44 | } 45 | ``` 46 | 47 | This code will generate and output a PlantUML diagram definition that looks like this: 48 | 49 | ``` 50 | @startuml 51 | !include 52 | 53 | ' Structurizr.SystemContextView: SystemContext 54 | title Software System - System Context 55 | 56 | LAYOUT_WITH_LEGEND() 57 | 58 | Enterprise_Boundary(SomeEnterprise, "Some Enterprise") { 59 | System(SoftwareSystem__33c0d9d, "Software System", "My software system.") 60 | Person(User__378734a, "User", "A user of my software system.") 61 | Rel_Right(User__378734a, SoftwareSystem__33c0d9d, "Uses") 62 | } 63 | @enduml 64 | ``` 65 | 66 | If you copy/paste this into [PlantUML online](http://www.plantuml.com/plantuml/), you will get something like this: 67 | 68 | ![A simple C4-PlantUML diagram](images/c4-plantuml-getting-started.png) 69 | 70 | __Mark containers or components as Database__ 71 | 72 | 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. 73 | 74 | 75 | ```c# 76 | Container webApplication = softwareSystem.AddContainer("Web Application", "Delivers content", "Java and spring MVC"); 77 | Container database = softwareSystem.AddContainer("Database", "Stores information", "Relational Database Schema"); 78 | // Additional mark it as database 79 | database.SetIsDatabase(true); 80 | user.Uses(webApplication, "uses", "HTTP"); 81 | webApplication.Uses(database, "Reads from and writes to", "JDBC").SetDirection(DirectionValues.Right); 82 | 83 | var containerView = views.CreateContainerView(softwareSystem, "containers", ""); 84 | containerView.AddAllElements(); 85 | 86 | using (var stringWriter = new StringWriter()) 87 | { 88 | var plantUmlWriter = new C4PlantUmlWriter(); 89 | plantUmlWriter.Write(containerView, stringWriter); 90 | Console.WriteLine(stringWriter.ToString()); 91 | } 92 | ``` 93 | 94 | This code will generate and output a PlantUML diagram definition that looks like this: 95 | 96 | ``` 97 | @startuml 98 | !include 99 | 100 | ' Structurizr.ContainerView: containers 101 | title Software System - Containers 102 | 103 | LAYOUT_WITH_LEGEND() 104 | 105 | Person(User__378734a, "User", "A user of my software system.") 106 | System_Boundary(SoftwareSystem__33c0d9d, "Software System") { 107 | ContainerDb(SoftwareSystem__Database__202c666, "Database", "Relational Database Schema", "Stores information") 108 | Container(SoftwareSystem__WebApplication__2004eee, "Web Application", "Java and spring MVC", "Delivers content") 109 | } 110 | Rel(User__378734a, SoftwareSystem__WebApplication__2004eee, "uses", "HTTP") 111 | Rel_Right(SoftwareSystem__WebApplication__2004eee, SoftwareSystem__Database__202c666, "Reads from and writes to", "JDBC") 112 | @enduml 113 | ``` 114 | 115 | You will get something like this: 116 | 117 | ![A simple C4-PlantUML diagram](images/c4-plantuml-getting-started2.png) 118 | 119 | ## Benefits of using C4-PlantUML with Structurizr 120 | 121 | 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: 122 | 123 | 1. Rather than looking after a collection of disjointed PlantUML diagram definitions, you can create many PlantUML diagrams from a single model and keep them all up to date easily, especially if integrated with your continuous build server and build pipeline. 124 | 1. The naming of elements and the definition of relationships between elements _remains consistent across diagrams_. 125 | 1. The software architecture model at the component level can be created by extracting components from a codebase, using _static analysis and reflection techniques_. 126 | -------------------------------------------------------------------------------- /Structurizr.AdrTools/AdrTools/AdrToolsImporter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using Structurizr.Documentation; 10 | 11 | namespace Structurizr.AdrTools 12 | { 13 | 14 | public class AdrToolsImporter 15 | { 16 | 17 | private readonly Regex TitleRegex = new Regex("^# \\d*\\. (.*)$", RegexOptions.Multiline); 18 | private readonly Regex DateRegex = new Regex("^Date: (\\d\\d\\d\\d-\\d\\d-\\d\\d)$", RegexOptions.Multiline); 19 | private readonly Regex StatusRegex = new Regex("## Status\\n\\n(\\w*)", RegexOptions.Multiline); 20 | 21 | private const string SupercededAlternativeSpelling = "Superceded"; 22 | 23 | private Workspace _workspace; 24 | private DirectoryInfo _path; 25 | 26 | public AdrToolsImporter(Workspace workspace, DirectoryInfo path) 27 | { 28 | if (workspace == null) 29 | { 30 | throw new ArgumentException("A workspace must be specified."); 31 | } 32 | 33 | if (path == null) 34 | { 35 | throw new ArgumentException("The path to the architecture decision records must be specified."); 36 | } 37 | 38 | if (!Directory.Exists(path.FullName)) 39 | { 40 | throw new ArgumentException(path.FullName + " does not exist."); 41 | } 42 | 43 | _workspace = workspace; 44 | _path = path; 45 | } 46 | 47 | public ISet ImportArchitectureDecisionRecords() 48 | { 49 | return ImportArchitectureDecisionRecords(null); 50 | } 51 | 52 | public ISet ImportArchitectureDecisionRecords(SoftwareSystem softwareSystem) 53 | { 54 | HashSet decisions = new HashSet(); 55 | 56 | IEnumerable markdownFiles = _path.GetFiles().Where(f => f.Name.EndsWith(".md")); 57 | 58 | // first create an index of filename -> ID 59 | Dictionary index = new Dictionary(); 60 | foreach (FileInfo file in markdownFiles) 61 | { 62 | index.Add(file.Name, ExtractIntegerIdFromFileName(file)); 63 | } 64 | 65 | foreach (FileInfo file in markdownFiles) 66 | { 67 | string id = ExtractIntegerIdFromFileName(file); 68 | DateTime date = new DateTime(); 69 | string title = ""; 70 | DecisionStatus status = DecisionStatus.Proposed; 71 | string content = File.ReadAllText(file.FullName, Encoding.UTF8); 72 | content = content.Replace("\r", ""); 73 | Format format = Format.Markdown; 74 | 75 | title = ExtractTitle(content); 76 | date = ExtractDate(content); 77 | status = ExtractStatus(content); 78 | 79 | foreach (string filename in index.Keys) { 80 | content = content.Replace(filename, CalculateUrl(softwareSystem, index[filename])); 81 | } 82 | 83 | Decision decision = _workspace.Documentation.AddDecision(softwareSystem, id, date, title, status, format, content); 84 | decisions.Add(decision); 85 | } 86 | 87 | return decisions; 88 | } 89 | 90 | private string CalculateUrl(SoftwareSystem softwareSystem, string id) 91 | { 92 | if (softwareSystem == null) { 93 | return "#/:" + UrlEncode(id); 94 | } 95 | else 96 | { 97 | return "#" + UrlEncode(softwareSystem.CanonicalName) + ":" + UrlEncode(id); 98 | } 99 | } 100 | 101 | private string UrlEncode(string value) 102 | { 103 | return WebUtility.UrlEncode(value).Replace("+", "%20"); 104 | } 105 | 106 | private string ExtractIntegerIdFromFileName(FileInfo file) 107 | { 108 | return "" + Convert.ToInt64(file.Name.Substring(0, 4)); 109 | } 110 | 111 | private string ExtractTitle(string content) 112 | { 113 | MatchCollection matchCollection = TitleRegex.Matches(content); 114 | if (matchCollection.Count > 0) 115 | { 116 | return matchCollection[0].Groups[1].Value; 117 | } 118 | else 119 | { 120 | return "Untitled"; 121 | } 122 | } 123 | 124 | private DateTime ExtractDate(string content) 125 | { 126 | MatchCollection matchCollection = DateRegex.Matches(content); 127 | if (matchCollection.Count > 0) 128 | { 129 | string dateAsString = matchCollection[0].Groups[1].Value; 130 | DateTime date; 131 | DateTime.TryParseExact(dateAsString, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out date); 132 | return date; 133 | } 134 | else 135 | { 136 | return new DateTime(); 137 | } 138 | } 139 | 140 | private DecisionStatus ExtractStatus(string content) 141 | { 142 | MatchCollection matchCollection = StatusRegex.Matches(content); 143 | if (matchCollection.Count > 0) 144 | { 145 | string status = matchCollection[0].Groups[1].Value; 146 | 147 | if (status == SupercededAlternativeSpelling) 148 | { 149 | return DecisionStatus.Superseded; 150 | } 151 | else 152 | { 153 | DecisionStatus decisionStatus; 154 | Enum.TryParse(status, out decisionStatus); 155 | 156 | return decisionStatus; 157 | } 158 | } 159 | else 160 | { 161 | return DecisionStatus.Proposed; 162 | } 163 | } 164 | 165 | } 166 | } -------------------------------------------------------------------------------- /docs/structurizr-annotations.md: -------------------------------------------------------------------------------- 1 | # Structurizr annotations 2 | 3 | Structurizr for .NET includes some custom code annotation attributes that you can add to your code. These serve to either make it explicit how components should be extracted from your codebase, or they help supplement the software architecture model. 4 | 5 | The attributes can be found in the [Structurizr.Annotations](https://nuget.org/packages/Structurizr.Annotations) NuGet package, which is a very small standalone JAR file containing only the Structurizr annotations. All attributes have a runtime retention policy, so they will be present in the compiled bytecode. 6 | 7 | ## [Component] 8 | 9 | A type-level annotation that can be used to signify that the annotated type (an interface or class) can be considered to be a "component". The properties are as follows: 10 | 11 | - Description: The description of the component (optional). 12 | - Technology: The technology of component (optional). 13 | 14 | ## [CodeElement] 15 | 16 | A type-level annotation that can be used to signify that the annotated type can be considered to be a supporting type for a component. The properties are as follows: 17 | 18 | - Description: The description of the code element (optional). 19 | 20 | ## [UsedByPerson] 21 | 22 | A type-level annotation that can be used to signify that the named person uses the component on which this annotation is placed, creating a relationship form the person to the component. The properties are as follows: 23 | 24 | - Name: The name of the person (required). 25 | - Description: The description of the relationship (optional). 26 | - Technology: The technology of relationship (optional). 27 | 28 | ## [UsedBySoftwareSystem] 29 | 30 | A type-level annotation that can be used to signify that the named software system uses the component on which this annotation is placed, creating a relationship from the software system to the component. The properties are as follows: 31 | 32 | - Name: The name of the software system (required). 33 | - Description: The description of the relationship (optional). 34 | - Technology: The technology of relationship (optional). 35 | 36 | ## [UsedByContainer] 37 | 38 | A type-level annotation that can be used to signify that the named container uses the component on which this annotation is placed, creating a relationship from the container to the component. The properties are as follows: 39 | 40 | - Name: The name of the container (required). 41 | - Description: The description of the relationship (optional). 42 | - Technology: The technology of relationship (optional). 43 | 44 | If the container resides in the same software system as the component, the simple name can be used to identify the container (e.g. "Database"). Otherwise, the full canonical name of the form "Software System/Container" must be used (e.g. "Some Other Software System/Database"). 45 | 46 | ## [UsesSoftwareSystem] 47 | 48 | A type-level annotation that can be used to signify that the component on which this annotation is placed has a relationship to the named software system, creating a relationship from the component to the software system. The properties are as follows: 49 | 50 | - Name: The name of the software system (required). 51 | - Description: The description of the relationship (optional). 52 | - Technology: The technology of relationship (optional). 53 | 54 | ## [UsesContainer] 55 | 56 | A type-level annotation that can be used to signify that the component on which this annotation is placed has a relationship to the named container, creating a relationship from the component to the container. The properties are as follows: 57 | 58 | - Name: The name of the container (required). 59 | - Description: The description of the relationship (optional). 60 | - Technology: The technology of relationship (optional). 61 | 62 | If the container resides in the same software system as the component, the simple name can be used to identify the container (e.g. "Database"). Otherwise, the full canonical name of the form "Software System/Container" must be used (e.g. "Some Other Software System/Database"). 63 | 64 | ## [UsesComponent] 65 | 66 | A field-level annotation that can be used to supplement the existing relationship (i.e. add a description and/or technology) between two components. 67 | 68 | When using the various component finder strategies, Structurizr for .NET will identify components along with the relationships between those components. Since this is typically done using reflection against the compiled bytecode, you'll notice that the description and technology properties of the resulting relationships is always empty. The ```[UsesComponent]``` annotation provides a simple way to ensure that such information is added into the model. 69 | 70 | The properties are as follows: 71 | 72 | - Description: The description of the relationship (required). 73 | - Technology: The technology of relationship (optional). 74 | 75 | ## Example 76 | 77 | Here are some examples of the annotations, which have been used to create the following diagram. 78 | 79 | ![](images/structurizr-annotations-1.png) 80 | 81 | ```csharp 82 | [Component(Description = "Serves HTML pages to users.", Technology = "ASP.NET MVC")] 83 | [UsedByPerson("User", Description = "Uses", Technology = "HTTPS")] 84 | class HtmlController 85 | { 86 | 87 | [UsesComponent("Gets data using")] 88 | private IRepository repository = new EfRepository(); 89 | 90 | } 91 | ``` 92 | 93 | ```csharp 94 | [Component(Description = "Provides access to data stored in the database.", Technology = "C#")] 95 | public interface IRepository 96 | { 97 | 98 | string GetData(long id); 99 | 100 | } 101 | ``` 102 | 103 | ```csharp 104 | [UsesContainer("Database", Description = "Reads from", Technology = "Entity Framework")] 105 | class EfRepository : IRepository 106 | { 107 | 108 | public string GetData(long id) 109 | { 110 | return "..."; 111 | } 112 | 113 | } 114 | ``` 115 | 116 | See [StructurizrAnnotations.cs](https://github.com/structurizr/dotnet/blob/master/Structurizr.Reflection.Examples/StructurizrAnnotations.cs) for the full source code illustrating how to use the various annotations in conjunction with the reflection-based component finder. The resulting diagrams can be found at [https://structurizr.com/share/38341](https://structurizr.com/share/38341). A Mono.Cecil based version of the example is also available; see its own [StructurizrAnnotations.cs](https://github.com/structurizr/dotnet/blob/master/Structurizr.Cecil.Examples/StructurizrAnnotations.cs) for source code and [https://structurizr.com/share/38339](https://structurizr.com/share/38339) for the resulting diagrams. -------------------------------------------------------------------------------- /Structurizr.Reflection/Analysis/TypeMatcherComponentFinderStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Structurizr.Analysis 5 | { 6 | 7 | /// 8 | /// Implements a component finder strategy which uses a collection of objects to identify 9 | /// components and their dependencies. 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | public class TypeMatcherComponentFinderStrategy : ComponentFinderStrategy 16 | { 17 | 18 | private ComponentFinder _componentFinder; 19 | 20 | /// 21 | public ComponentFinder ComponentFinder 22 | { 23 | get { return _componentFinder; } 24 | set 25 | { 26 | _componentFinder = value; 27 | } 28 | } 29 | 30 | private HashSet _componentsFound = new HashSet(); 31 | 32 | private ITypeRepository _typeRepository; 33 | private List _typeMatchers = new List(); 34 | private List _supportingTypesStrategies = new List(); 35 | 36 | /// 37 | /// Creates a new instance of for identifying 38 | /// components using the provided type matchers. 39 | /// 40 | /// 41 | /// An array of objects implementing that will be used to identify components in the 42 | /// provided assembly. 43 | /// 44 | public TypeMatcherComponentFinderStrategy(params ITypeMatcher[] typeMatchers) 45 | { 46 | this._typeMatchers.AddRange(typeMatchers); 47 | } 48 | 49 | /// 50 | public void BeforeFindComponents() 51 | { 52 | _typeRepository = new ReflectionTypeRepository(_componentFinder.Namespace, _componentFinder.Exclusions); 53 | foreach (SupportingTypesStrategy strategy in _supportingTypesStrategies) 54 | { 55 | strategy.TypeRepository = _typeRepository; 56 | } 57 | } 58 | 59 | /// 60 | public IEnumerable FindComponents() 61 | { 62 | foreach (Type type in _typeRepository.GetAllTypes()) 63 | { 64 | foreach (ITypeMatcher typeMatcher in this._typeMatchers) 65 | { 66 | if (typeMatcher.Matches(type)) 67 | { 68 | Component component = ComponentFinder.Container.AddComponent( 69 | type.Name, 70 | type, 71 | typeMatcher.GetDescription(), 72 | typeMatcher.GetTechnology()); 73 | _componentsFound.Add(component); 74 | } 75 | } 76 | } 77 | 78 | return _componentsFound; 79 | } 80 | 81 | /// 82 | public void AfterFindComponents() 83 | { 84 | // before finding dependencies, let's find the types that are used to implement each component 85 | foreach (Component component in _componentsFound) 86 | { 87 | foreach (CodeElement codeElement in component.CodeElements) 88 | { 89 | codeElement.Visibility = _typeRepository.FindVisibility(codeElement.Type); 90 | codeElement.Category = _typeRepository.FindCategory(codeElement.Type); 91 | } 92 | 93 | foreach (SupportingTypesStrategy strategy in _supportingTypesStrategies) 94 | { 95 | foreach (string type in strategy.FindSupportingTypes(component)) 96 | { 97 | if (ComponentFinder.Container.GetComponentOfType(type) == null) 98 | { 99 | CodeElement codeElement = component.AddSupportingType(type); 100 | codeElement.Visibility = _typeRepository.FindVisibility(type); 101 | codeElement.Category = _typeRepository.FindCategory(type); 102 | } 103 | } 104 | } 105 | } 106 | 107 | foreach (Component component in ComponentFinder.Container.Components) 108 | { 109 | if (component.Type != null) 110 | { 111 | AddEfferentDependencies(component, component.Type, new HashSet()); 112 | 113 | // and repeat for the supporting types 114 | foreach (CodeElement codeElement in component.CodeElements) 115 | { 116 | AddEfferentDependencies(component, codeElement.Type, new HashSet()); 117 | } 118 | } 119 | } 120 | } 121 | 122 | private void AddEfferentDependencies(Component component, string type, HashSet typesVisited) 123 | { 124 | typesVisited.Add(type); 125 | 126 | foreach (string referencedTypeName in _typeRepository.GetReferencedTypes(type)) 127 | { 128 | Component destinationComponent = ComponentFinder.Container.GetComponentOfType(referencedTypeName); 129 | if (destinationComponent != null) 130 | { 131 | if (component != destinationComponent) 132 | { 133 | component.Uses(destinationComponent, ""); 134 | } 135 | } 136 | else if (!typesVisited.Contains(referencedTypeName)) 137 | { 138 | AddEfferentDependencies(component, referencedTypeName, typesVisited); 139 | } 140 | } 141 | } 142 | 143 | /// 144 | /// Adds a strategy for identifying supporting types of components identified by this strategy. 145 | /// 146 | /// 147 | /// A instance to use for identifying supporting types. 148 | /// 149 | public void AddSupportingTypesStrategy(SupportingTypesStrategy strategy) 150 | { 151 | if (strategy != null) 152 | { 153 | _supportingTypesStrategies.Add(strategy); 154 | strategy.TypeRepository = _typeRepository; 155 | } 156 | } 157 | 158 | } 159 | } -------------------------------------------------------------------------------- /Structurizr.Cecil/Util/CustomAttributeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Mono.Cecil; 6 | 7 | namespace Structurizr.Cecil.Util 8 | { 9 | /// 10 | /// Provides extension methods for working with custom attributes. 11 | /// 12 | static class CustomAttributeExtensions 13 | { 14 | /// 15 | /// Retrieves custom attributes of a specified type that are applied to a specified type. 16 | /// 17 | /// The type to inspect. 18 | /// The type of attribute to search for. 19 | /// 20 | /// A collection of custom attributes which match , or an empty enumeration if 21 | /// no such attribute is found. 22 | /// 23 | public static IEnumerable ResolvableAttributes(this TypeDefinition klassType) 24 | where TAttribute : Attribute 25 | { 26 | var attributeType = typeof(TAttribute).GetTypeInfo(); 27 | 28 | foreach (var customAttribute in klassType.CustomAttributes) 29 | { 30 | if (!customAttribute.Is()) continue; 31 | 32 | var arguments = customAttribute.ConstructorArguments.Select(a => a.Value).ToArray(); 33 | 34 | var createdAttribute = (TAttribute)Activator.CreateInstance(typeof(TAttribute), arguments); 35 | foreach (var attributeProperty in customAttribute.Properties) 36 | { 37 | var propertyInfo = attributeType.GetDeclaredProperty(attributeProperty.Name); 38 | propertyInfo.SetValue(createdAttribute, attributeProperty.Argument.Value); 39 | } 40 | 41 | yield return createdAttribute; 42 | } 43 | } 44 | 45 | /// 46 | /// Retrieves custom attributes of a specified type that are applied to a specified field. 47 | /// 48 | /// The field to inspect. 49 | /// The type of attribute to search for. 50 | /// 51 | /// A collection of custom attributes which match , or an empty enumeration if 52 | /// no such attribute is found. 53 | /// 54 | public static IEnumerable ResolvableAttributes(this FieldDefinition field) 55 | where TAttribute : Attribute 56 | { 57 | var attributeType = typeof(TAttribute).GetTypeInfo(); 58 | 59 | foreach (var customAttribute in field.CustomAttributes) 60 | { 61 | if (!customAttribute.Is()) continue; 62 | 63 | var arguments = customAttribute.ConstructorArguments.Select(a => a.Value).ToArray(); 64 | 65 | var createdAttribute = (TAttribute)Activator.CreateInstance(typeof(TAttribute), arguments); 66 | foreach (var attributeProperty in customAttribute.Properties) 67 | { 68 | var propertyInfo = attributeType.GetDeclaredProperty(attributeProperty.Name); 69 | propertyInfo.SetValue(createdAttribute, attributeProperty.Argument.Value); 70 | } 71 | 72 | yield return createdAttribute; 73 | } 74 | } 75 | 76 | /// 77 | /// Retrieves custom attributes of a specified type that are applied to a specified property. 78 | /// 79 | /// The property to inspect. 80 | /// The type of attribute to search for. 81 | /// 82 | /// A collection of custom attributes which match , or an empty enumeration if 83 | /// no such attribute is found. 84 | /// 85 | public static IEnumerable ResolvableAttributes(this PropertyDefinition property) 86 | where TAttribute : Attribute 87 | { 88 | var attributeType = typeof(TAttribute).GetTypeInfo(); 89 | 90 | foreach (var customAttribute in property.CustomAttributes) 91 | { 92 | if (!customAttribute.Is()) continue; 93 | 94 | var arguments = customAttribute.ConstructorArguments.Select(a => a.Value).ToArray(); 95 | 96 | var createdAttribute = (TAttribute)Activator.CreateInstance(typeof(TAttribute), arguments); 97 | foreach (var attributeProperty in customAttribute.Properties) 98 | { 99 | var propertyInfo = attributeType.GetDeclaredProperty(attributeProperty.Name); 100 | propertyInfo.SetValue(createdAttribute, attributeProperty.Argument.Value); 101 | } 102 | 103 | yield return createdAttribute; 104 | } 105 | } 106 | 107 | /// 108 | /// Retrieves custom attributes of a specified type that are applied to a specified parameter. 109 | /// 110 | /// The property to inspect. 111 | /// The type of attribute to search for. 112 | /// 113 | /// A collection of custom attributes which match , or an empty enumeration if 114 | /// no such attribute is found. 115 | /// 116 | public static IEnumerable ResolvableAttributes(this ParameterDefinition parameter) 117 | where TAttribute : Attribute 118 | { 119 | var attributeType = typeof(TAttribute).GetTypeInfo(); 120 | 121 | foreach (var customAttribute in parameter.CustomAttributes) 122 | { 123 | if (!customAttribute.Is()) continue; 124 | 125 | var arguments = customAttribute.ConstructorArguments.Select(a => a.Value).ToArray(); 126 | 127 | var createdAttribute = (TAttribute)Activator.CreateInstance(typeof(TAttribute), arguments); 128 | foreach (var attributeProperty in customAttribute.Properties) 129 | { 130 | var propertyInfo = attributeType.GetDeclaredProperty(attributeProperty.Name); 131 | propertyInfo.SetValue(createdAttribute, attributeProperty.Argument.Value); 132 | } 133 | 134 | yield return createdAttribute; 135 | } 136 | } 137 | 138 | private static bool Is(this CustomAttribute attribute) 139 | { 140 | return attribute.AttributeType.FullName == typeof(TAttribute).FullName; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Structurizr.Cecil/Analysis/TypeMatcherComponentFinderStrategy.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | using Mono.Cecil; 4 | 5 | using Structurizr.Cecil; 6 | 7 | namespace Structurizr.Analysis 8 | { 9 | /// 10 | /// Implements a component finder strategy which uses a collection of objects to identify 11 | /// components and their dependencies. 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | public class TypeMatcherComponentFinderStrategy : ComponentFinderStrategy 18 | { 19 | /// 20 | public ComponentFinder ComponentFinder { get; set; } 21 | 22 | private HashSet _componentsFound = new HashSet(); 23 | 24 | private AssemblyDefinition _primaryAssembly; 25 | private ITypeRepository _typeRepository; 26 | private List _typeMatchers = new List(); 27 | private List _supportingTypesStrategies = new List(); 28 | 29 | /// 30 | /// Creates a new instance of for identifying 31 | /// components from the provided assembly using the provided type matchers. 32 | /// 33 | /// 34 | /// An instance representing the assembly to analyze. 35 | /// 36 | /// 37 | /// An array of objects implementing that will be used to identify components in the 38 | /// provided assembly. 39 | /// 40 | public TypeMatcherComponentFinderStrategy(AssemblyDefinition assembly, 41 | params ITypeMatcher[] typeMatchers) 42 | { 43 | this._primaryAssembly = assembly; 44 | this._typeMatchers.AddRange(typeMatchers); 45 | } 46 | 47 | /// 48 | public void BeforeFindComponents() 49 | { 50 | _typeRepository = new CecilTypeRepository( 51 | _primaryAssembly, 52 | ComponentFinder.Namespace, 53 | ComponentFinder.Exclusions); 54 | foreach (SupportingTypesStrategy strategy in _supportingTypesStrategies) 55 | { 56 | strategy.TypeRepository = _typeRepository; 57 | } 58 | } 59 | 60 | /// 61 | public IEnumerable FindComponents() 62 | { 63 | foreach (TypeDefinition type in _typeRepository.GetAllTypes()) 64 | { 65 | foreach (ITypeMatcher typeMatcher in this._typeMatchers) 66 | { 67 | if (typeMatcher.Matches(type)) 68 | { 69 | Component component = ComponentFinder.Container.AddComponent( 70 | type.Name, 71 | type.GetAssemblyQualifiedName(), 72 | typeMatcher.GetDescription(), 73 | typeMatcher.GetTechnology()); 74 | _componentsFound.Add(component); 75 | } 76 | } 77 | } 78 | 79 | return _componentsFound; 80 | } 81 | 82 | /// 83 | public void AfterFindComponents() 84 | { 85 | // before finding dependencies, let's find the types that are used to implement each component 86 | foreach (Component component in _componentsFound) 87 | { 88 | foreach (CodeElement codeElement in component.CodeElements) 89 | { 90 | codeElement.Visibility = _typeRepository.FindVisibility(codeElement.Type); 91 | codeElement.Category = _typeRepository.FindCategory(codeElement.Type); 92 | } 93 | 94 | foreach (SupportingTypesStrategy strategy in _supportingTypesStrategies) 95 | { 96 | foreach (string type in strategy.FindSupportingTypes(component)) 97 | { 98 | if (ComponentFinder.Container.GetComponentOfType(type) == null) 99 | { 100 | CodeElement codeElement = component.AddSupportingType(type); 101 | codeElement.Visibility = _typeRepository.FindVisibility(type); 102 | codeElement.Category = _typeRepository.FindCategory(type); 103 | } 104 | } 105 | } 106 | } 107 | 108 | foreach (Component component in ComponentFinder.Container.Components) 109 | { 110 | if (component.Type != null) 111 | { 112 | AddEfferentDependencies(component, component.Type, new HashSet()); 113 | 114 | // and repeat for the supporting types 115 | foreach (CodeElement codeElement in component.CodeElements) 116 | { 117 | AddEfferentDependencies(component, codeElement.Type, new HashSet()); 118 | } 119 | } 120 | } 121 | } 122 | 123 | private void AddEfferentDependencies(Component component, string type, HashSet typesVisited) 124 | { 125 | typesVisited.Add(type); 126 | 127 | foreach (string referencedTypeName in _typeRepository.GetReferencedTypes(type)) 128 | { 129 | Component destinationComponent = ComponentFinder.Container.GetComponentOfType(referencedTypeName); 130 | if (destinationComponent != null) 131 | { 132 | if (component != destinationComponent) 133 | { 134 | component.Uses(destinationComponent, ""); 135 | } 136 | } 137 | else if (!typesVisited.Contains(referencedTypeName)) 138 | { 139 | AddEfferentDependencies(component, referencedTypeName, typesVisited); 140 | } 141 | } 142 | } 143 | 144 | /// 145 | /// Adds a strategy for identifying supporting types of components identified by this strategy. 146 | /// 147 | /// 148 | /// A instance to use for identifying supporting types. 149 | /// 150 | public void AddSupportingTypesStrategy(SupportingTypesStrategy strategy) 151 | { 152 | if (strategy != null) 153 | { 154 | _supportingTypesStrategies.Add(strategy); 155 | strategy.TypeRepository = _typeRepository; 156 | } 157 | } 158 | } 159 | } 160 | --------------------------------------------------------------------------------