Skip to content

Commit

Permalink
Finished first draft of the blog post.
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Schoenherr committed Dec 8, 2019
1 parent d1d4d0a commit f585dc3
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 118 deletions.
121 changes: 79 additions & 42 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ ifndef::env-github[]
:icons: font
endif::[]

= Living Diagrams

:toc:
:toc-placement!:

== Living Documentation Lab

=== Motivation
== Motivation
Documentation is hard. Every programmer knows this. It's also boring and repetitive, and the result often looks ugly
and is hard to read (because not all programmers are poets). The fact that written documentation is usually outdated
before it has been finished doesn't make it any better.
Expand All @@ -30,19 +30,19 @@ documentation is the code itself. It's the only thing that won't become outdated
complete.

But code is difficult to understand. If you have ever tried to learn about a code base without written documentation,
you'll know that it is very, very hard to glean the truth from it.
you'll know that it is very, very hard to glean anything useful from it.

==== Living Diagrams
=== Living Diagrams

===== History
The idea to generate UML diagrams from source code is not new. Actually, it has a long history in software development,
==== History
The idea to generate UML diagrams from source code isn't new. Actually, it has a long history in software development,
a history that is often quite disappointing. Many tools provide reverse engineering capabilities, for example:

* UML tool suites
* IDEs
* Dedicated generators

====== UML tool suites
===== UML tool suites

There are tools like Together, Rational Rose, Enterprise Architect etc. They often do a decent job generating UML
diagrams and sometimes even provide full Roundtrip-Engineering capabilities. However, they are usually extremely
Expand All @@ -51,13 +51,11 @@ In addition, the resulting diagrams are difficult to integrate into the actual d
itself, so often users will find themselves resorting to pasting image files that are - again - difficult to keep
up-to-date.

====== IDEs
===== IDEs

IDEs like Netbeans, Eclipse or IntelliJ play somewhat in the same league. Their reverse engineering capabilities are
usually less thorough, but also come at a cheaper price. Sadly, though, the IDEs' UML modules share most of the other
negative aspects of their expensive brethren.

For example, here is a diagram as IntelliJ IDEA would render it:
negative aspects of their expensive brethren. For example, here is a diagram as IntelliJ IDEA would render it:

.Test classes hierarchy generated using IntelliJ IDEA
image::idea-package-example.png[Package diagram rendered by IntelliJ IDEA, float=left]
Expand All @@ -68,45 +66,75 @@ It is also impossible to exclude unwanted parts from the diagram or make referen

'''

====== Dedicated generators
===== Dedicated generators
Quite contrary to the previously mentioned reverse engineering tools, dedicated UML diagram generators are usually cheap
or even free of charge, for example the https://www.spinellis.gr/umlgraph/javadoc/index.html[UMLGraph JavaDoc doclet].
Since they can usually be made part of automated toolchains, it is often a lot easier to integrate their products into
the parts of the documentation that live in close proximity to the code, for example JavaDocs. Despite these positive
aspects, it's usually still very difficult to influence what the diagrams display, and how.

===== A new concept: Living Documentation
'Living Domumentation' is a term coíned by Cyrille Martraire, a french software engineer who has written a
book[https://leanpub.com/livingdocumentation] about the topic. The core principles of Living
Documentation are:
==== A new concept: Living Documentation
'Living Documentation' is a term coined by Gojko Adzik in his book https://gojko.net/books/specification-by-example/?src=/resources.html[Specification by example].
His work (that is mostly about BDD - Behavior Driven Development) gave inspiration to the work of Cyrille Martraire, a
french software engineer who tries to take the approach to a new level in his book https://leanpub.com/livingdocumentation[Living Documentation].
The core principles of Living Documentation are:

* It is *reliable* - all documentation products are always accurate and in sync with the actual code
* Producing and updating it is *low effort*
* It is both a product and a medium of *collaboration* between all involved people
* It is *insightful* for ist consumers and sheds light on the important aspects of the system

One of the crucial insights taken from Cyrille's book is that it *is* possible to produce a lot of helpful documentation
artifacts by traversing the code using automated generators.
artifacts by traversing the code using automated generators. One of the key difference to most existing tools that
generate documentation (including diagrams) from code is that the tool chain is written in such a way that it permits
the development team to adapt and enhance what comes out of it, producing results that both have real value and are
still as close to the single source of truth (the code) as possible.

In this blog post, we build on this concept but concentrate on a subset of the ideas from Living Documentation:
**Living Diagrams**. We'd like to generate UML Diagrams, namely UML Class Diagrams, directly from the code. So, let us
intruce the PlantUML Class Diagram Generator.

== The PlantUML Class Diagram Generator

The PlantUML Class Diagram Generator is a tool that produces PlantUML Class diagrams from Java source code.

.What is PlantUML?
****
https://plantuml.com/[PlantUML] is a tool that generates various types of UML diagrams from a written specification. It
has a simple but powerful language for describing UML diagrams, with further annotation and styling capabilities that
allow producing diagrams that are both useful and nice looking. Some of the supported diagram types are:
* Use Case
* Class
* Sequence
* Activity
* Component
****

=== The PlantUML Class Diagram Generator
Our generator is different from all theses examples because it follows the ideas of Living Documentation.
The PlantUML Class Diagram Processor creates only class diagrams and can be used to document class hierarchies that
live in a specific package. Due to PlantUML's ability to import diagrams into other diagrams, it is also possible to
display whole package hierarchies in a songle diagram. However, thanks to its underlying concepts, these diagrams
can be tailored to show only classes that are relevant for specific use cases, so they won't overwhelm the reader with
superfluous or redundant information.

==== Design principles
The generator is designed around a limited set of principles derived from the idea of Living Documentation:
=== Design principles
The generator is designed around a limited set of principles derived from the ideas of Living Documentation:

[horizontal]
Relevance::
The programmer decides what elements from the sources should show up in generated diagrams, so they are always relevant
to the documentation
to the use case depicted
Proximity::
Diagram controls are an intrinsic part of the source code
Diagram controls are an intrinsic part of the source code, so its unlikely they'll become outdated
Configurability::
Diagram Controls give the developer a lot of control over what is generated
Diagram Controls give the developer a lot of control over what is generated and may be annotated with additional
information, e.g. comments
Currentness::
Diagrams are re-generated with every build

==== Annotation library
At the moment, the annotation library contains only a few annotations that can be used for class diagrams:
=== Annotation library
At the moment, the annotation library contains following annotations for class diagrams:

link:annotations/src/main/java/com/comsysto/livingdoc/annotation/plantuml/PlantUmlClass.java[@PlantUmlClass]::
This is the main annotation to be used for class diagrams. When added to a Java type (interface, class or enum), a
Expand All @@ -124,7 +152,7 @@ link:annotations/src/main/java/com/comsysto/livingdoc/annotation/plantuml/PlantU
Can be used to draw additional dependency relations between types that are not directly connected via an
association.

==== Annotation processor
=== Annotation processor
The annotation processor is a normal Java annotation processor that can be included easily as a Java compiler argument -
either using the programmer's favorite Java IDE's project configuration, or as part of the build process.
The annotation processor produces a model of the elements to be rendered in the resulting diagrams and then outputs the
Expand All @@ -149,11 +177,9 @@ This setting may be used to completely disable the processor at compilation time
|``true``
|===


=== Examples
As a simple example, the test sources contain an artificial class hierarchy that models different types of vehicles. The
following diagram is auto-generated using the annotation processor - you can verify this by changing something in the
code and re-run the build.
The test sources contain an artificial class hierarchy that models different types of vehicles and is used as a (quite
simple) example. Please have a look at the diagram - it is auto-generated using the annotation processor:

==== Example 1: The whole test classes hierarchy

Expand All @@ -162,7 +188,7 @@ ifdef::env-github[]
image::package_class.png[Annotation processor classes, float=right]
endif::[]
ifndef::env-github[]
plantuml::annotation-processors/out/package_class.puml[imagesoutdir="./annotation-processors/doc", float=right]
plantuml::annotation-processors/out/package_class.puml[imagesoutdir="./annotation-processors/doc"]
endif::[]

The first example displays the hierarchy of all annotated classes in the package. We find it notable how clean and
Expand Down Expand Up @@ -196,6 +222,7 @@ DiagramId:: The diagram ID is the part of the filename that comes before the ``_
is therefore **package**. The ground vehicles diagram's ID is **ground-vehicles**.

'''

== Annotation processor internals

In this section, we will look at the internal structure of the annotation processor. To reach our goal to auto-generate
Expand Down Expand Up @@ -269,7 +296,6 @@ at runtime. To process annotations at compile time, however, requires some addit
@SupportedAnnotationTypes("com.comsysto.livingdoc.annotation.plantuml.PlantUmlClass") // <3>
@SupportedOptions({KEY_SETTINGS_DIR, KEY_OUT_DIR, KEY_ENABLED}) // <4>
@SupportedSourceVersion(SourceVersion.RELEASE_8) // <5>
@AutoService(Processor.class) // <6>
public class PlantUmlClassDiagramProcessor extends AbstractProcessor { // <1>
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
Expand All @@ -289,9 +315,21 @@ framework when it calls the ``process(..)`` method are required here - in our ca
<3> Processors need to define the options they proccess - those put on the ``javac`` command line using the ``-A``
parameter
<4> Processors need to define the Java source version they understand
<5> The
<5> The current version of the processor has been tested with Java 8

==== Add the required meta information
In addition to the implementation processor, we have to add the following file:

.META-INF/services/javax.annotation.processing.Processor
[source]
----
com.comsysto.livingdoc.annotation.processors.plantuml.PlantUmlClassDiagramProcessor
----

This file, containing only the fully qualified class name of the processor, causes it to be registered with the
annotation processing environment.

TIP: Alternatively, https://github.com/google/auto/tree/master/service:[Google Autoservice] may be used to autogenerate this file.

=== Data Model

Expand All @@ -310,7 +348,6 @@ The first version of something is seldom perfect. There is a lot more that could

* Support for additional class diagram elements
* Support for other diagram types
* Refactoring

=== Support for additional class diagram elements

Expand All @@ -332,10 +369,10 @@ For us, generating class diagrams is only a first step. Going further, we'd like
types. The class diagram was the obvious place to start, since its features closely match the information that can be
gleaned from the information harvested by the annotation processing environment.

=== Refactoring

The current model can be extended to support at least some of those things. We may, however, reach a point in the future
where our first shot at modelling reaches its limits


== Conclusion
In this blog post, we have shown that it is well possible to generate useful diagrams from source code by giving the
developers a big deal of influence on the outcome using Java annotations, along with a tool set that, while still being
in an early stage of development, can already produce very nice and fully accurate class diagrams. We hope that this
blog post will be the first in a series in which we will try to extend its capabilities into a complete tool suite that
helps developers in writing documentation that is accurate, close to the code, and always up to date.

Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
import static org.apache.commons.lang3.BooleanUtils.toBoolean;

import com.comsysto.livingdoc.annotation.plantuml.PlantUmlClass;
import com.comsysto.livingdoc.annotation.plantuml.PlantUmlDependency;
import com.comsysto.livingdoc.annotation.plantuml.PlantUmlExecutable;
import com.comsysto.livingdoc.annotation.plantuml.PlantUmlNote;
import com.comsysto.livingdoc.annotation.plantuml.PlantUmlNotes;
import com.comsysto.livingdoc.annotation.plantuml.PlantUmlDependency;
import com.comsysto.livingdoc.annotation.processors.plantuml.model.ClassDiagram;
import com.comsysto.livingdoc.annotation.processors.plantuml.model.DiagramId;
import com.comsysto.livingdoc.annotation.processors.plantuml.model.TypePart;
import com.google.auto.service.AutoService;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
Expand All @@ -40,7 +39,6 @@
import java.util.Properties;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
Expand All @@ -56,7 +54,6 @@
@SupportedAnnotationTypes("com.comsysto.livingdoc.annotation.plantuml.PlantUmlClass")
@SupportedOptions({KEY_SETTINGS_DIR, KEY_OUT_DIR, KEY_ENABLED})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
@Slf4j
@PlantUmlClass(diagramIds = PlantUmlClassDiagramProcessor.DIAGRAM_ID)
@PlantUmlDependency(target = "ClassDiagram", description = "generates/processes")
Expand All @@ -71,8 +68,7 @@ public class PlantUmlClassDiagramProcessor extends AbstractProcessor {
private final Configuration freemarkerConfiguration = new Configuration(Configuration.VERSION_2_3_23);

private String settingsDir;
private String outDir;

private String outDir;

public PlantUmlClassDiagramProcessor() {
freemarkerConfiguration.setTemplateLoader(new ClassTemplateLoader(this.getClass(), ""));
Expand All @@ -81,7 +77,7 @@ public PlantUmlClassDiagramProcessor() {
@Override
@PlantUmlExecutable
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
log.debug("Environment: {}", processingEnv.getOptions().toString());
log.debug("Starting processing of PlantUML annotations.");

settingsDir = processingEnv.getOptions().getOrDefault(KEY_SETTINGS_DIR, DEF_SETTINGS_DIR);
outDir = processingEnv.getOptions().getOrDefault(KEY_OUT_DIR, DEF_OUT_DIR);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.comsysto.livingdoc.annotation.processors.plantuml.PlantUmlClassDiagramProcessor
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@PlantUmlClass(diagramIds = { "package", "ground-vehicles" })
@PlantUmlNote(body = "A vehicle that drives on the ground", position = Position.TOP)
@PlantUmlNote(body = "Multiple notes may be attached to a type", position = Position.RIGHT)
@PlantUmlNote(body = "Multiple notes may be\nattached to a type", position = Position.RIGHT)
public abstract class GroundVehicle extends Vehicle {

@PlantUmlField
Expand Down
Binary file modified doc/images/annotation-processor_class.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
84 changes: 42 additions & 42 deletions doc/images/annotation-processor_class.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified doc/images/ground-vehicles_class.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit f585dc3

Please sign in to comment.