Skip to content

Commit

Permalink
Merge pull request #5 from klu2/update-structurizr-spring
Browse files Browse the repository at this point in the history
major: update to structurizr 2 and spring 3, migrate to java in order to reduce dependencies
  • Loading branch information
rspiegl authored Jul 19, 2024
2 parents 97c4d35 + 9cd5e00 commit 60f2ae2
Show file tree
Hide file tree
Showing 25 changed files with 522 additions and 506 deletions.
19 changes: 12 additions & 7 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
`maven-publish`
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
signing
`java-library`
}

description = "Spring Boot AutoConfigure Support for the Structurizr Client"
Expand All @@ -19,22 +20,26 @@ repositories {
}

dependencies {
api(platform(libs.spring.boot.bom))
kapt(platform(libs.spring.boot.bom))
implementation(platform(libs.spring.boot.bom))
annotationProcessor(platform(libs.spring.boot.bom))

api(libs.structurizr.core)
api(libs.structurizr.client)

api(libs.structurizr.export.c4plantuml)
api(libs.architectureicons)
api(libs.structurizr.export)
implementation(libs.structurizr.client)

// let that be an API dependency in order publish @SpringBootApplication
api("org.springframework.boot:spring-boot-autoconfigure")
runtimeOnly("org.springframework.boot:spring-boot-starter")

kapt("org.springframework.boot:spring-boot-configuration-processor")
compileOnly("org.springframework.boot:spring-boot-configuration-processor")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")

testImplementation("org.springframework.boot:spring-boot-test")

testImplementation(platform(libs.junit.jupiter.bom))
testImplementation(libs.architectureicons)
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

java {
Expand Down
9 changes: 6 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
[versions]

structurizr = "1.12.2"
spring-boot = "2.7.1"
structurizr = "2.2.0"
spring-boot = "3.3.1"
junit-jupiter = "5.10.3"


[libraries]

structurizr-core = { module = "com.structurizr:structurizr-core", version.ref = "structurizr" }
structurizr-export = { module = "com.structurizr:structurizr-export", version.ref = "structurizr" }
structurizr-client = { module = "com.structurizr:structurizr-client", version.ref = "structurizr" }

structurizr-export-c4plantuml = { module = "io.cloudflight.structurizr:structurizr-export-c4plantuml", version = "1.0.0" }
architectureicons = { module = "io.cloudflight.architectureicons:architectureicons", version = "0.3.0" }

spring-boot-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot" }

junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit-jupiter" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.cloudflight.architecture.structurizr;

import com.structurizr.model.Model;

import javax.annotation.Nonnull;

/**
* Implementors of this interface will be called after all spring components have been initialized but before all implicit
* relationships are created and before all views are generated
*
* @author Klaus Lehner
*/
public interface ModelPostProcessor {
/**
* Do some action on the {@link Model} after the spring context has been initialized
*
* @param model the C4 Model
*/
void postProcess(@Nonnull Model model);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.cloudflight.architecture.structurizr;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
* Use this class as a starter method for your applications. It wraps SpringApplication and logs an error if there is one
*
* @author Klaus Lehner
*/
public final class SpringStructurizr {

private static final Logger LOG = LoggerFactory.getLogger(SpringStructurizr.class);

private SpringStructurizr() {
}

public static ConfigurableApplicationContext run(Class<?> architectureClass) {
try {
return SpringApplication.run(architectureClass);
} catch (Exception ex) {
LOG.error("Error at creating Structurizr model", ex);
throw ex;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.cloudflight.architecture.structurizr;

import com.structurizr.view.ViewSet;

import javax.annotation.Nonnull;

/**
* Implement this interface if you want to create {@link com.structurizr.view.View}s for your {@link com.structurizr.model.Model}.
* The reason why this is done in a separate method is to ensure that all {@link org.springframework.stereotype.Component}s which
* might manipulate the model during startup phase have been initialized and that the {@link com.structurizr.model.Model} is complete.
*
* @author Klaus Lehner
*/
public interface ViewProvider {
/**
* <p>This method is called automatically after the whole {@link org.springframework.context.ApplicationContext} is refreshed,
* thus the {@link com.structurizr.model.Model} definition should be complete.</p>
*
* <p>Implementations of this method might create one or more {@link com.structurizr.view.View} inside this method</p>
*
* @param viewSet the {@link ViewSet} of the {@link com.structurizr.Workspace} after the whole {@link com.structurizr.model.Model}
* has been initialized
*/
void createViews(@Nonnull ViewSet viewSet);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.cloudflight.architecture.structurizr;

import com.structurizr.Workspace;

import javax.annotation.Nonnull;

public interface WorkspaceExportService {

void exportWorkspace(@Nonnull Workspace workspace);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.cloudflight.architecture.structurizr.autoconfigure;

import com.structurizr.Workspace;
import com.structurizr.api.WorkspaceApiClient;
import com.structurizr.encryption.AesEncryptionStrategy;
import com.structurizr.model.CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy;
import com.structurizr.model.Model;
import io.cloudflight.architecture.structurizr.internal.service.StructurizrService;
import io.cloudflight.architecture.structurizr.internal.service.export.C4PlantUmlExportService;
import io.cloudflight.architecture.structurizr.internal.service.export.StructurizrWorkspaceExportService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* @author Klaus Lehner
*/
@Configuration
@ComponentScan(basePackageClasses = StructurizrService.class)
@EnableConfigurationProperties(value = StructurizrProperties.class)
class StructurizrAutoConfiguration {

@Bean
StructurizrService structurizrService(Workspace workspace, ApplicationContext applicationContext) {
return new StructurizrService(workspace, applicationContext);
}

@Bean
Model model(Workspace workspace, StructurizrProperties properties) {
if (properties.workspace().isAddImplicitRelationships()) {
workspace.getModel().setImpliedRelationshipsStrategy(
new CreateImpliedRelationshipsUnlessAnyRelationshipExistsStrategy());
}
return workspace.getModel();
}


@Bean
Workspace workspace(StructurizrProperties config) {
return new Workspace(config.workspace().name(), config.workspace().description());
}

@Configuration
@ConditionalOnProperty(value = "structurizr.export.structurizr.enabled", havingValue = "true")
class StructurizrExportConfiguration {
@Bean
WorkspaceApiClient structurizrClient(StructurizrProperties config) {
StructurizrProperties.StructurizrExport structurizr = config.export().structurizr();
WorkspaceApiClient structurizrClient = new WorkspaceApiClient(structurizr.url(), structurizr.key(), structurizr.secret());

// Add client-side encryption if configured
if (structurizr.encryptionPassphrase() != null) {
structurizrClient.setEncryptionStrategy(new AesEncryptionStrategy(structurizr.encryptionPassphrase()));
}
structurizrClient.setWorkspaceArchiveLocation(structurizr.workspaceArchiveLocation());
return structurizrClient;
}

@Bean
StructurizrWorkspaceExportService structurizrWorkspaceExporter(StructurizrProperties properties, WorkspaceApiClient client) {
return new StructurizrWorkspaceExportService(properties, client);
}
}

@Configuration
@ConditionalOnProperty(value = "structurizr.export.c4-plant-uml.enabled", havingValue = "true")
class C4PlantUmlExportConfiguration {
@Bean
C4PlantUmlExportService c4PlantUmlExportService(StructurizrProperties properties) {
return new C4PlantUmlExportService(properties);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.cloudflight.architecture.structurizr.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

import java.io.File;

/**
* Configuration properties for Structurizr.
*
* @author Klaus Lehner
*/
@ConfigurationProperties("structurizr")
public record StructurizrProperties(Workspace workspace, Export export) {

public record Export(
StructurizrExport structurizr,
C4PlantUmlExport c4PlantUml
) {
}

/**
* @param enabled if <code>true</code>, the workspace will be exported to a Structurizr server
* @param url the url of the Structurizr server, the default is <code>https://api.structurizr.com</code>
* @param id
* @param key Workspace API key
* @param secret Workspace API secret
* @param encryptionPassphrase Workspace client-side encryption passphrase
* @param workspaceArchiveLocation Directory to use to archive workspaces.
*/
public record StructurizrExport(
@DefaultValue("false")
boolean enabled,
@DefaultValue("https://api.structurizr.com")
String url,
@DefaultValue("0")
Long id,
String key,
String secret,
String encryptionPassphrase,
File workspaceArchiveLocation
) {
}

/**
* @param enabled if <code>true</code>, the Workspace will be exported using the PlantUML exporter
* @param outputDirectory
*/
public record C4PlantUmlExport(
@DefaultValue("false")
boolean enabled,
@DefaultValue("build/c4PlantUml")
File outputDirectory
) {
}

/**
* @param name workspace name
* @param description workspace description
* @param isAddImplicitRelationships Propagates all relationships from children to their parents.
*/
public record Workspace(
String name,
String description,
@DefaultValue("true")
boolean isAddImplicitRelationships
) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.cloudflight.architecture.structurizr.internal.service;

import com.structurizr.Workspace;
import io.cloudflight.architecture.structurizr.ModelPostProcessor;
import io.cloudflight.architecture.structurizr.ViewProvider;
import io.cloudflight.architecture.structurizr.WorkspaceExportService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;

/**
* @author Klaus Lehner
*/
@Order(StructurizrService.ORDER)
public class StructurizrService implements CommandLineRunner {

private final Workspace workspace;
private final ApplicationContext applicationContext;

static final int ORDER = 0;

public StructurizrService(Workspace workspace, ApplicationContext applicationContext) {
this.workspace = workspace;
this.applicationContext = applicationContext;
}

@Override
public void run(String... args) throws Exception {
applicationContext.getBeansOfType(ModelPostProcessor.class)
.values()
.stream()
.sorted(AnnotationAwareOrderComparator.INSTANCE)
.forEach(modelPostProcessor -> modelPostProcessor.postProcess(workspace.getModel()));

applicationContext.getBeansOfType(ViewProvider.class)
.values()
.forEach(viewProvider -> viewProvider.createViews(workspace.getViews()));

applicationContext.getBeansOfType(WorkspaceExportService.class)
.values()
.stream()
.sorted(AnnotationAwareOrderComparator.INSTANCE)
.forEach(exporter -> exporter.exportWorkspace(workspace));
}
}
Loading

0 comments on commit 60f2ae2

Please sign in to comment.