Giter Site home page Giter Site logo

archifacts / archifacts Goto Github PK

View Code? Open in Web Editor NEW
51.0 51.0 1.0 475 KB

archifacts is a library to extract your architectural concepts out of your application's code

Home Page: https://www.archifacts.org

License: Apache License 2.0

Java 100.00%
architecture archunit asciidoc c4-model documentation java kotlin

archifacts's Introduction

Build Maven Central License

archifacts is a free (Apache 2.0 license) library for describing and detecting architectural building blocks and their relationships in your Java applications.

archifacts heavily relies on ArchUnit which analyzes the Java bytecode. With the help of descriptors archifacts identifies building blocks, relationships and containers of your application and builds a model.

Having this model in place you can visualize your application's architecture or verify it against certain rules.

While we already have some support for the former, the latter is subject of future work.

Experimental API

Caution! archifacts is in a very early state. The API is not intended to be stable. We might introduce breaking changes at any time if we think it improves the overall user experience. Please be aware of this, if you decide to use archifacts.

Nevertheless we try to reduce the breaking changes to a minimum, but there is no guarantee.

Usage

Gradle

implementation 'org.archifacts:archifacts-core:0.5.0'

Maven

<dependency>
    <groupId>org.archifacts</groupId>
    <artifactId>archifacts-core</artifactId>
    <version>0.5.0</version>
</dependency>

How to get started

Take a look at our Fraktalio demo application example and Spring Restbucks example to get an idea about how to use archifacts.

Fraktalio

The project documents the Fraktalio demo application which is a Spring Boot and Axon Framework based microservice system, implemented in Java and Kotlin.

The generated documentation is deployed using GitHub Actions. You can find it here

Spring Restbucks

The project documents the Spring Restbucks application which is a Spring Boot and jMolecules based modulithic system, implemented in Java.

The generated documentation is deployed using GitHub Actions. You can find it here

Why is it called 'archifacts'?

archifacts is a made-up word out of architects, artifacts and facts.

architects who want to visualize or verify their architecture are the main target group of the library.

Every class, interface or enum in your application is treated as an artifact. Artifact is the common base class for all these elements and therefore the foundation of archifacts.

archifacts is all about facts as the model is extracted from bytecode. With this approach archifacts tackles outdated documentation. The model contents are facts.

How can I contribute?

The most helpful contribution in this early project phase is feedback. Feedback about bugs, missing features, misconceptions, successes, whatever. We would like to get in touch with the library's users to improve archifacts.

License

ArchUnit is published under the Apache License 2.0, see http://www.apache.org/licenses/LICENSE-2.0 for details.

archifacts's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

nils-christian

archifacts's Issues

Add possibility to represent modules as C4 Components

archifacts models modules of a monolith as containers. Due to this converation with @simonbrowndotje they should be modelled as components.

Unfortunately, the Structurizr Java API is limited and does not provide all the features we need to model modules as components appropriately.

As an alternative we could think about generating PlantUML directly using the C4-PlantUML-Stdlib or generating Structurizr DSL Code.

At first glance the DSL approach seems to be the better choice as it abstracts from the concrete technology (PlantUML).

It seems to be possible to parse the generated DSL files using StructurizrDslParser.

As part of this issue it should be possible to decide how modules should be represented in the C4 model.
archifacts should support representing them as containers (distributed system) or components (modular monolith).

C4ModelTransformer crashes with different container descriptors delivering the same container name

Assume I have two ArtifactContainerDescriptors, one of ArtifactContainerType A and another one of ArtifactContainerType B. Now assume that I have two classes C and D. C is described by A with the name X, D is described by B, also with name X.

.addContainerDescriptor( new ArtifactContainerDescriptor( ) {
	
	@Override
	public ArtifactContainerType type( ) {
		return ArtifactContainerType.of( "A" );
	}
	
	@Override
	public Optional<String> containerNameOf( JavaClass javaClass ) {
		if (javaClass.isEquivalentTo( C.class )) {
			return Optional.of( "X" );
		} else {
			return Optional.empty( );
		}
	}
} )
.addContainerDescriptor( new ArtifactContainerDescriptor( ) {
	
	@Override
	public ArtifactContainerType type( ) {
		return ArtifactContainerType.of( "B" );
	}
	
	@Override
	public Optional<String> containerNameOf( JavaClass javaClass ) {
		if (javaClass.isEquivalentTo( D.class )) {
			return Optional.of( "X" );
		} else {
			return Optional.empty( );
		}
	}
} )

While trying to transform the resulting Application, the C4ModelTransformer will crash:

Exception in thread "main" java.lang.IllegalArgumentException: A container named 'X' already exists for this software system.
at com.structurizr.model.Model.addContainer(Model.java:213)
at com.structurizr.model.SoftwareSystem.addContainer(SoftwareSystem.java:113)
at org.archifacts.integration.c4.model.C4ModelTransformer.lambda$transformContainers$1(C4ModelTransformer.java:79)
at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1224)

C4ModelTransformer crashes with duplicated names in the same container

I have multiple classes with the same simple name in my project. This leads to an exception during the transformation in the C4ModelTransformer.

Assume you have two classes A in different packages and build an application with them (without a container descriptor or with a container descriptor classifing them into the same container). Once you use the transformer, an exception occurs: "java.lang.IllegalArgumentException: A component named 'A' already exists for this container."

See also #9.

Minor JavaDoc issues

This is nitpicking, but there are some minor issues recarding the JavaDoc I noted (I know, it is not final yet, but still):

  • One can use labels in the linking. So instead of {@link ArtifactContainer}s one can write {@link ArtifactContainer ArtifactContainers} to make sure that the s is part of the link.
  • I am missing some kind of a hint regarding thread safety. For instance I see that the writing access to Application is not thread-safe, but the reading access makes defensive copies. It is always nice to know whether a certain class is threadsafe (and if so - in what aspects).
  • I can see that the JavaDoc contains "a" multiple times where "an" would be correct. For instance: "Registers a {@link ArtifactContainerDescriptor}" should be "Registers an {@link ArtifactContainerDescriptor}" because of the vowel.
  • The JavaDoc can currently not be created because the JavaDoc of TargetBasedArtifactRelationshipDescriptor.sources references an invalid parameter.

Publish releases to Maven Central

At least releases should be published to maven central. Instead of using the maven-release-plugin people advice to use the workflow which is described in this blog post for publishing to Maven Central with GitHub Actions.

Add convenience methods to create ArtifactContainerDescriptor for default cases

Similar to #5, I found some presumable default cases for ArtifactContainerDescriptors. I tend to categorize my building blocks usually by the package name. Therefore one could imagine methods to create descriptors based on a partial match of the full qualified name or based on a regexp maching the full qualified name of the java class in question:

ContainerDescriptorHelper.forFQNContaining( ArtifactContainerType.of( "Module" ), ".read.", "Read Models" ) 

or

ContainerDescriptorHelper.forFQNMatching( ArtifactContainerType.of( "Layer" ), ".*\.frontend\..*", "Frontend" ) 

Add Fraktalio example

Fraktalio provides three intersting Axon based example applications. It would be great to use these applications to demonstrate the archifacts capabilities.

Add convenience methods to create BuildingBlockDescriptor for default cases

This is certainly always a difficult topic, but it would be nice to have some convenience methods to create BuildingBlockDescriptors for some default cases. While writing descriptors for my application I noticed that I can reduce most of them to three cases:

  • JavaClass ends with a certain name
  • JavaClass is assignable to a certain class
  • JavaClass is annotated with a certain annotation

I wrote the methods as part of a helper class, but they could (theoretically) be part of the BuildingBlockDescriptor interface as well.

applicationBuilder.addBuildingBlockDescriptor( BuildingBlockDescriptorHelper.forAssignableTo( BuildingBlockType.of( "Repository" ), Repository.class ) );
applicationBuilder.addBuildingBlockDescriptor( BuildingBlockDescriptorHelper.forSimpleNameEndingWith( BuildingBlockType.of( "Projection" ), "Projection" ) );

Add possibility to supress certain classes

I am not sure if this should be in the scope of the framework, but it would be nice to have some possibility to supress certain classes. For instance, when doing a "normal" import on my project I get a lot of nested builder classes which are completely irrelevant to the big picture.

For instance: I have a building block named "Projection" in my application. The actual class MonatsstatistikProjection is correctly identified as such. However, now I have a remaining MiscArtifact named MonatsstatistikProjection$MonatsstatistikProjectionBuilder.

I am not sure what would be a good solution for this.

  • Some kind of addSupressedBuildingBlockDescriptor method?
  • Provide some methods like printOverview with an additional parameter to remove the miscellaneous artefacts?
  • Do nothing and leave this in the scope of the ArchUnit import of the JavaClasses.

Add order/priority for descriptors

As stated by @OLibutzki in PR #29 some kind of order/priority for the descriptors would be useful. I had the same thought while writing some of the descriptors. The easiest way would probably be a getOrder method returning an Integer (default 0). If multiple descriptors match, the order would be used to resolve the tie (lower order wins). Only if two descriptors match the same JavaClass and both return the same value, an exception would be thrown.

Change the component names in the C4ModelTransformer

Also regarding #9 and #19 I wonder if one shouldn't be able to change the component names during the transformation process. Currently always the simple name of the component it used. This is fine for smaller projects, but in larger projects we get ambiguity. For instance, it would be nice if one could choose to use the FQN with the "short" package names (e.g., d.e.MyComponent for de.example.MyComponent).

Consider making AsciiDocElement a functional interface

Currently the interface AsciiDocElement has a single method, allowing to do something like the following:

final AsciiDoc asciiDoc = new AsciiDoc( );
asciiDoc.addDocElement( ( ) -> "= Some header");

In order to make sure that code like this remains compatible, we should consider making AsciiDocElement a functional interface.

Provide alternatives to writing the documentation into a file

While writing the documentation (like in the AsciiDoc class) into a file is probably often sufficient, it is still pretty limited. Maybe other output methods (stream, string, ...) should be considered or even separate the "serialization" of the documentation completely from the actual documentation.

Prioritize descriptors by the order they are added

Currently, there is no priorization ragarding the descriptors. In #31 we discussed some ideas, but I'm not nonvinced that these ideas are the right ones.

As a first simple approach I's like to prioritize the descriptors by the order they are added to the application builder.

Having this simple logic the one who build the application can fine-tune the descriptors's priority. Without having this notion there might be no way to get out of certain conflicts.

Provide a module-info file

The framework uses currently Java 11. It should at least be discussed if a module-info file should be therefore included. Some of the used libraries (like apiguardian or log4j-api) include one as well. Such a module-info file would have to be maintained though.

This could be a starting point:

module archifacts.core {
	
	requires com.tngtech.archunit;
	requires org.apache.logging.log4j;
	
}

Choose a license for the framework

The project should choose a suitable license (e.g., MIT oder Apache 2), add the LICENSE file in the root directory and add the license in the pom.xml.

Provide descriptors for Spring Framework

Similar to #24 we should provide some descriptors for the Spring framework. We have to discuss for which parts we should provide such descriptors, but we should at least provide some kind of starting point with this integration. Some thoughts:

  • Recognize @Component, @Service, @Repository, @Controller as components (named: "Component", "Service", "Repository", "Controller" of course)
  • Maybe @Configuration as well?
  • One could of course recognize which entities a CrudRepository handles, but this would rather be part of a Spring JPA integration - so I think we should only focus on the above stereotypes from above (spring-context).

Separate integration of reporting from descriptors

Currently there is only one integration-aggregator containing both the reporting transformation (e.g., AsciiDoc) and the actual descriptors (e.g, jmolecules). If we start to write more integrations (#24, #25), we should probably separate those integration aspects.

Container descriptor swallows building blocks

Assume I have three classes A, B, C and build an application with them:

ApplicationBuilder applicationBuilder = new ApplicationBuilder( );
JavaClasses javaClasses = new ClassFileImporter( ).importClasses( A.class, B.class, C.class );
Application application = applicationBuilder.buildApplication( javaClasses );
application.printOverview( );

The output is as expected:

Artifacts without module:
    <MiscArtifact> C
    <MiscArtifact> B
    <MiscArtifact> A
Relationships:

Now I add a simple ContainerDescriptor, assigning all blocks to "N/A". The output is now:


<N/A> N/A
  Building Blocks:
  Miscellaneous Artifacts:
    <MiscArtifact> A
  External Artifacts:
Artifacts without module:
Relationships:

Note that A and B are now missing.

Inconsistent module names

[INFO] org.archifacts:archifacts-parent                                   [pom]
[INFO] archifacts-core                                                    [jar]
[INFO] archifacts-asciidoc                                                [jar]
[INFO] archifacts-c4-model                                                [jar]
[INFO] archifacts-integration-aggregator                                  [pom]
[INFO] archifacts-c4-asciidoc                                             [jar]
[INFO] archifacts-jmolecules                                              [jar]
[INFO] archifacts-axon                                                    [jar]
[INFO] archifacts-spring                                                  [jar]
[INFO] archifacts-examples-jmolecules-spring-data-jpa                     [jar]
[INFO] archifacts-examples-aggregator                                     [pom]
[INFO] archifacts-aggregator                                              [pom]

Removing the name tag from all of the child modules might be sufficient.

C4ModelTransformer crashes with anonymous classes

Due to my BuildingBlockDescriptorHelper beeing part of the classpath scan, I have accidentally some anonymous classes in my application. Those are stored as misc artifacts. So far not a problem, but when they are transformed in the C4ModelTransformer, an exception is thrown: "java.lang.IllegalArgumentException: A component name must be provided".

The reason is, that the simple name of anonymous classes is always an empty string (as stated in the documentation of getSimpleName). As misc artifacts always return the simple name of the class as a name, the empty string is passed on to the structurizr API which throws the exception.

I wonder what I would expect to happen. Should anonymous classes be part of the application in the first place? Should they be filtered out during the transformation? Or maybe another name should be used as fallback?

Render methods should probably use platform line separator

I can see multiple appearances of "\n" as a line separator. Although not wrong, it is the line separator on unix systems. I would assume that most tools could handle this, but it would probably be "correcter" to use the system dependent line separator. System.lineSeparator can be used for this.

Standardize behaviour with null parameters

As discussed in #34, we should standardize the behaviour of methods/constructors/etc. when it comes to null parameters. In some cases we throw a NullPointerException, in some other cases an IllegalArgumentException. I prefer Objects.requireNonNull, but only because it allows to perform assigment and validation in a single line. If we want to throw an IllegalArgumentException instead, we should probably add/use a util method for this.

ApplicationBuilder could return this to allow method chaining

When using builders, it is a common pattern that the builder methods return this to allow method chaining. It would be nice if I could write this

new ApplicationBuilder( )
		.addBuildingBlockDescriptor( new EventDescriptor( ) )
		.addContainerDescriptor( new TestContainerDescriptor( ) )
		.buildApplication( javaClasses );

instead of

final ApplicationBuilder applicationBuilder = new ApplicationBuilder( );
applicationBuilder.addBuildingBlockDescriptor( new EventDescriptor( ) );
applicationBuilder.addContainerDescriptor( new TestContainerDescriptor( ) );

Add readme

Add a readme file to explain the project's purpose.

Relationship classification for JMolecule depends on building block descriptors

While writing test cases for the JMolecules integration (PR #35), I noticed that the relationships depend on the building block descriptors. This leads to the rather unexpected behaviour that the relationships are only classified correctly if and only if the dependent block descriptors are used as well. This can be seen in the new test case assertThat_source_based_artifact_relationship_descriptor_are_recognized which has to register two block descriptors. If one comments those two lines out, the test fails.

Add an AsciiDocElement for tables

While generating an architecture documentation for our application, we noticed that it was often useful to list certain elements in tables. It would be nice if archifacts-asciidoc would provide a simple implementation for a table. I was thinking about an extendable single-column table with a generic and a label extractor function. Another default implementation could extend it and display Named elements.

public class TableDocElement<T> {

  public TableDocElement(final String label, final Iterable<T> elements, final Function<T, String> labelExtractor) {...}
  
}

public final class NamedDocElement<T extends Named> extends TableDocElement<T> {
  ...  
}

We should of course extend the example with such a table.

ApplicationBuilder's add methods should check for null values

Currently the ApplicationBuilder does not check for null values when new elements are added. This leads to NullPointerExceptions at a later point. For instance, I can do the following:

final ApplicationBuilder applicationBuilder = new ApplicationBuilder();
applicationBuilder.addContainerDescriptor( null );	
applicationBuilder.buildApplication( javaClasses );

The NPE doesn't occur until after I actually build the application in line 3 though. It would be better to avoid null values as early as possible.

Exception in case of multiple matching building block descriptors lists all descriptors

In case we have multiple building block descriptors matching a given class, the IllegalStateException lists ALL building block descriptors instead of the descriptors actually matching.

For instance:

  • Create three descriptors A, B, C.
  • A and B return always true, C returns always false
  • Build an application out of an abritary class D
  • The resulting exception says "java.lang.IllegalStateException: For D multiple BuildingBlockDescriptors match: A, B, C"

Provide descriptors for Axon Framework

We should introduce a module which contains descriptors for the axon framework, esspecially regaring Events, Commands and queries and the artifacts which send/publish them and the artifacts which handle them.

Identifying handing components is easy due to the correspoding annotations. Detecting sending/publishing is harder. An option is a heuristic that says: Whenever an event, a command or a query is built/instaniated, it will be sent, so we could scann the application for those building blocks to be instantiated and treat that as the source of a certain relationship.

How to detect that a buidling block instantiated: The constructor is called or a static method of the building block's class is called which returns a building block instance (factory method) might be a good starting point.

Equals on descriptor types and roles leads to unexpected behaviour

Currently the types/roles of building blocks, containers, and relationships perform an equals based on equality of the String. Although this surely is a valid approach, it leads to weird behaviour in case that two descriptors have types with the same names.

Consider for instance the relationship descriptors for Axon, which have types with the same name (and are wrongly even of the same instance). Now it is not possible to query the Application only for the QueryHandlerDescriptor, as all relationships are found.

I suggest the following:

  • Perform the equality check of types/roles based on reference equality.
  • Make sure that all our own descriptors have an unique instance (per class).

ArchUnit Lang API Integration

Currently Archifacts focuses on visualizing a system's architecture.

With the model in place it would be great to express rules. The model is backed by ArchUnit and therefore I would like to benefit from ArchUnit's Lang API as much as possible.

The question is how to integrate into this API.

This issue is more or less an epic to discuss possible ways to express architecture rules based on the Archifacts model.

Inconsistent behaviour of query/getter methods regarding immutability

I noticed that some of the query/getter methods in archifacts return a immutable collections while other do not. This leads to an inconsistent behaviour of the API.

I can't do this:
application.getArtifacts().add( ...)
But I can do this:
application.getArtifactsOfType(...).add(...)
I can't do this:
application.getRelationships().add(...);
But I can do this:
application.getBuildingBlocks().add(...);

In the very least we should document this behaviour. It would probably be better if all those methods would return an immutable collection.

Update ArchUnit Version

Hi,

Currently archifacts is using ArchUnit 1.0.1. archifacts should probably update to the latest 1.1.0 version, especially because the 1.0.1 does not work with Java 21.

Thank you and best regards

Nils

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.