diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..8f7fe96 --- /dev/null +++ b/contributing.md @@ -0,0 +1,13 @@ +--- +layout: default +title: Contributing +nav_order: 19 +permalink: /contributing +--- + +# Contributing + +## Can I submit a pull request? + +It depends on the nature of the change - please open an issue first to discuss it. +Unsolicited pull requests will likely be ignored/closed. \ No newline at end of file diff --git a/java/faq.md b/java/faq.md new file mode 100644 index 0000000..4ebb869 --- /dev/null +++ b/java/faq.md @@ -0,0 +1,43 @@ +--- +layout: default +title: FAQ +parent: Structurizr for Java +nav_order: 9 +permalink: /java/faq +--- + +# Frequently asked questions + +## How do I build from source? + +To build from the sources (you'll need git and Java 17+ installed): + +``` +git clone https://github.com/structurizr/java.git structurizr-java +cd structurizr-java +./gradlew +``` + +This will create two `.jar` files: + +- structurizr-client/build/libs/structurizr-client-{version}.jar +- structurizr-core/build/libs/structurizr-core-{version}.jar + +## Why are many classes final with package-protected members, and not open to extension? + +First and foremost, this repo is a client library for the Structurizr cloud service and on-premises installation. +It allows you to write Java code to create an in-memory object graph representing a software architecture model and +views (a "workspace"), serialize that to JSON, and upload it via a web API. +The workspace has an [OpenAPI definition](https://github.com/structurizr/json/), but this library also implements a +number of rules (think of them as the "business logic") to ensure that the workspace is valid. +These rules include, for example, ensuring that all containers with a software system have unique names, +and that you can't add components to a system context view. + +Removing the `final` modifier from the classes and leaving them open for extension allows you to bypass/break these rules, +which will likely lead to the serialized workspace definitions being incompatible with the various diagram rendering tools +(i.e. the Structurizr cloud service/on-premises installation/Lite, plus the PlantUML/Mermaid exporters). +The output of this library also needs to be compatible with client libraries written in other languages. + +You are welcome to fork this library for your own purposes. +Alternatively, you can build a thin wrapper around the library, to provide your own custom functionality, +or perhaps a more fluent API. \ No newline at end of file diff --git a/java/getting-started.md b/java/getting-started.md new file mode 100644 index 0000000..bd3fa49 --- /dev/null +++ b/java/getting-started.md @@ -0,0 +1,105 @@ +--- +layout: default +title: Getting started +parent: Structurizr for Java +nav_order: 1 +permalink: /java/getting-started +--- + +# Getting started + +Here is a quick overview of how to get started with Structurizr for Java so that you can create a workspace using Java code. +You can find a full code example at [GettingStarted.java](https://github.com/structurizr/examples/blob/main/java/src/main/java/com/structurizr/example/GettingStarted.java) +and the published workspace at [https://structurizr.com/share/25441](https://structurizr.com/share/25441). +See [structurizr-examples](https://github.com/structurizr/examples/tree/main/java/src/main/java/com/structurizr/example) for more examples. + +## 1. Dependencies + +The Structurizr for Java binaries are hosted on [Maven Central](https://repo1.maven.org/maven2/com/structurizr/structurizr-client/) +and the dependency coordinates for `structurizr-client` (which has a transient dependency on `structurizr-core`) is as follows. + + - `com.structurizr:structurizr-client` + +## 2. Create a Java program + +We'll start by creating a new Java class with a ```main``` method as follows: + +```java +public class GettingStarted { + + public static void main(String[] args) throws Exception { + // your Structurizr code will go here + } + +} +``` + +## 3. Create a model + +The first step is to create a workspace in which the software architecture model will reside. + +```java +Workspace workspace = new Workspace("Getting Started", "This is a model of my software system."); +Model model = workspace.getModel(); +``` + +Now let's add some elements to the model to describe a user using a software system. + +```java +Person user = model.addPerson("User", "A user of my software system."); +SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System", "My software system."); +user.uses(softwareSystem, "Uses"); +``` + +## 4. Create a view + +With the model created, we need to create some views with which to visualise it. + +```java +ViewSet views = workspace.getViews(); +SystemContextView contextView = views.createSystemContextView(softwareSystem, "SystemContext", "An example of a System Context diagram."); +contextView.addAllSoftwareSystems(); +contextView.addAllPeople(); +``` + +## 5. Add some colour and shapes + +Optionally, elements and relationships can be styled by specifying colours, sizes and shapes. + +```java +Styles styles = views.getConfiguration().getStyles(); +styles.addElementStyle(Tags.SOFTWARE_SYSTEM).background("#1168bd").color("#ffffff"); +styles.addElementStyle(Tags.PERSON).background("#08427b").color("#ffffff").shape(Shape.Person); +``` + +## 6. Next steps + +The code to this point will create a workspace, and now you have options as to what do to next. + +### 6a. Upload to the Structurizr cloud service + +The Structurizr cloud service provides a web API to get and put workspaces, +and an API client is provided via the [WorkspaceApiClient](https://github.com/structurizr/java/blob/master/structurizr-client/src/com/structurizr/api/WorkspaceApiClient.java) class. +Assuming that you have a cloud service account, have created a workspace, and have the workspace ID plus API key/secret pair: + +```java +WorkspaceApiClient client = new WorkspaceApiClient("key", "secret"); +client.putWorkspace(id, workspace); +``` + +### 6b: Upload to a Structurizr on-premises installation + +This is the same as above, with the exception that you'll need to additionally provide an API endpoint: + +```java +WorkspaceApiClient client = new WorkspaceApiClient("http://localhost:8080/api", "key", "secret"); +client.putWorkspace(id, workspace); +``` + +### 6c. Export to a JSON file + +To export the workspace to a JSON file: + +```java +WorkspaceUtils.saveWorkspaceToJson(workspace, new File("workspace.json")); +``` \ No newline at end of file diff --git a/java/images/implied-relationships.png b/java/images/implied-relationships.png new file mode 100644 index 0000000..dd2f86f Binary files /dev/null and b/java/images/implied-relationships.png differ diff --git a/java/implied-relationships.md b/java/implied-relationships.md new file mode 100644 index 0000000..a77113e --- /dev/null +++ b/java/implied-relationships.md @@ -0,0 +1,102 @@ +--- +layout: default +title: Implied relationships +parent: Structurizr for Java +nav_order: 3 +permalink: /java/implied-relationships +--- + +# Implied relationships + +By default, the Structurizr for Java library will not create implied relationships. +For example, imagine that you have two components in different containers: + +``` +SoftwareSystem softwareSystem = model.addSoftwareSystem("Software System"); +Container container1 = softwareSystem.addContainer("Container 1"); +Component component1 = container1.addComponent("Component 1"); + +Container container2 = softwareSystem.addContainer("Container 2"); +Component component2 = container2.addComponent("Component 2"); +``` + +And you create a relationship between them: + +``` +component1.uses(component2, "Sends data X to"); +``` + +At this point, the model contains a single relationship between the two components, +but there are three other implied relationships that could be added: + +![Implied relationships](images/implied-relationships.png) + +| Type | Description | +|----------|-----------------------------------------| +| Explicit | Component 1 sends data X to Component 2 | +| Implied | Container 1 Sends data X to Component 2 | +| Implied | Component 1 Sends data X to Container 2 | +| Implied | Container 1 Sends data X to Container 2 | + +To have the client library create these for you, set an `ImpliedRelationshipsStrategy` implementation on your model. +Provided implementations are as follows. + +## DefaultImpliedRelationshipsStrategy + +This strategy does not create any implied relationships. + +``` +model.setImpliedRelationshipsStrategy(new DefaultImpliedRelationshipsStrategy()); // default +component1.uses(component2, "Sends data X to"); +``` + +Relationships that exist in the model: + +| Type | Description | +|----------|-----------------------------------------| +| Explicit | Component 1 sends data X to Component 2 | + +## CreateImpliedRelationshipsUnlessSameRelationshipExistsStrategy + +This strategy creates implied relationships between all valid combinations of the parent elements, unless the same +relationship already exists between them. + +``` +model.setImpliedRelationshipsStrategy(new CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy()); +component1.uses(component2, "Sends data X to"); +``` + +Relationships that exist in the model: + +| Type | Description | +|----------|-----------------------------------------| +| Explicit | Component 1 sends data X to Component 2 | +| Implied | Container 1 Sends data X to Component 2 | +| Implied | Component 1 Sends data X to Container 2 | +| Implied | Container 1 Sends data X to Container 2 | + +Although appealing, this strategy typically results in an explosion of relationships in the model, +and therefore isn't particularly useful in real-world usage. + +## CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy + +This strategy creates implied relationships between all valid combinations of the parent elements, unless *any* +relationship already exists between them. + +``` +model.setImpliedRelationshipsStrategy(new CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy()); +container1.uses(container2, "Sends data to"); +component1.uses(component2, "Sends data X to"); +``` + +Relationships that exist in the model: + +| Type | Description | +|----------|-----------------------------------------| +| Explicit | Container 1 Sends data to Container 2 | +| Explicit | Component 1 sends data X to Component 2 | +| Implied | Component 1 Sends data X to Container 2 | +| Implied | Container 1 Sends data X to Component 2 | + +This strategy is useful when you want to show a summary relationship at higher levels in the model, +especially when multiple implied relationships could be created between elements. \ No newline at end of file diff --git a/java/workspace-api-client.md b/java/workspace-api-client.md new file mode 100644 index 0000000..9857233 --- /dev/null +++ b/java/workspace-api-client.md @@ -0,0 +1,88 @@ +--- +layout: default +title: Workspace API client +parent: Structurizr for Java +nav_order: 2 +permalink: /java/workspace-api +--- + +# Workspace API client + +The Structurizr cloud service and on-premises installation provide a web API to get and put workspaces, +and an API client is provided via the [WorkspaceApiClient](https://github.com/structurizr/java/blob/master/structurizr-client/src/com/structurizr/api/WorkspaceApiClient.java) class. + +Each workspace has its own API key and secret, the values for which can be found on the workspace settings page. +You will need these values (plus the workspace ID) when creating a `WorkspaceApiClient` instance: + +For the cloud service: + +```java +WorkspaceApiClient client = new WorkspaceApiClient("key", "secret"); +``` + +If you're using the on-premises installation, you'll need the three argument version of the constructor where you can also specify the API URL. + +```java +WorkspaceApiClient client = new WorkspaceApiClient("url", "key", "secret"); +``` + +## Usage + +The following operations are available on the API client. + +### Get workspace + +This allows you to get the content of a remote workspace. + +```java +Workspace workspace = client.getWorkspace(123456); +``` + +By default, a copy of the workspace (as a JSON document) is archived to the current working directory. You can modify this behaviour by calling ```setWorkspaceArchiveLocation```. A ```null``` value will disable archiving. + +### Put workspace + +This allows you to overwrite an existing remote workspace. +If the ```mergeFromRemote``` property (on the `WorkspaceApiClient` instance) is set to `true` (this is the default), +any layout information (i.e. the location of boxes on diagrams) is preserved where possible (i.e. where diagram elements haven't been renamed). + +```java +client.putWorkspace(123456, workspace); +``` + +### Locking/unlocking a workspace + +Paid cloud service workspaces and the on-premises installation have a workspace locking feature to prevent concurrent updates. + +```java +client.lockWorkspace(1234); +``` + +This method returns a boolean; ```true``` if the workspace could be locked, ```false``` otherwise. + +Similarly, you can unlock a workspace. + +```java +client.unlockWorkspace(1234); +``` + +This method also returns a boolean; ```true``` if the workspace could be unlocked, ```false``` otherwise. + +## Troubleshooting + +### SSL handshake errors + +SSL handshake errors are likely if using a self-signed certificate with the on-premises installation, +because the Structurizr client runtime won't trust a self-signed certificate by default. + +If this happens, you can use the ```javax.net.ssl.trustStore``` JVM option to point to your keystore. For example: + +``` +java -Djavax.net.ssl.trustStore=/some/path/to/keystore.jks YourJavaProgram +``` + +Alternatively, you can specify this property in your Java program: + +``` +System.setProperty("javax.net.ssl.trustStore", "/some/path/to/keystore.jks"); +``` \ No newline at end of file