Skip to content

Commit

Permalink
Review: Regenerate User Guide with added Onion Architecture (note tha…
Browse files Browse the repository at this point in the history
…t that part about FreezingArchRule was outdated as well). Adjusted the diagram a little, because I think it is easier if domain and adapters are grouped.

Issue: #174
Signed-off-by: Peter Gafert <[email protected]>
  • Loading branch information
codecholeric committed Jul 19, 2019
1 parent daa45b9 commit 223b78d
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 47 deletions.
4 changes: 2 additions & 2 deletions docs/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ services:
environment:
- JEKYLL_ENV=development
volumes:
- $PWD:/srv/jekyll
- $PWD/_config-dev.yml:/srv/jekyll/_config.yml
- ./:/srv/jekyll
- ./_config-dev.yml:/srv/jekyll/_config.yml
ports:
- 4000:4000
88 changes: 46 additions & 42 deletions docs/userguide/008_The_Library_API.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,87 +26,91 @@ An example setup for a simple 3-tier architecture can be found in <<Layer Checks

==== Onion Architecture

In an "onion architecture" (also known as "hexagonal architecture" or "ports and adapters"),
In an "Onion Architecture" (also known as "Hexagonal Architecture" or "Ports and Adapters"),
we can define domain packages and adapter packages as follows.

[source,java]
----
onionArchitecture()
.domainModel("com.myapp.domain.model")
.domainService("com.myapp.domain.service")
.application("com.myapp.application")
.adapter("cli", "com.myapp.adapter.cli")
.adapter("persistence", "com.myapp.adapter.persistence")
.adapter("rest", "com.myapp.adapter.rest");
.domainModels("com.myapp.domain.model..")
.domainServices("com.myapp.domain.service..")
.applicationServices("com.myapp.application..")
.adapter("cli", "com.myapp.adapter.cli..")
.adapter("persistence", "com.myapp.adapter.persistence..")
.adapter("rest", "com.myapp.adapter.rest..");
----

The semantic follows the descriptions in https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/.
More precisely, the following holds:

* The `domain` package is the core of the application. It consists of two parts.
. The `domainModel` packages contain the domain entities.
. The packages in `domainService` contains services that use the entities in the `domainModel` packages.
* The `application` packages contain services and configuration to run the application and use cases. It can use the items of the `domain` package but there must not be any dependency from the `domain` to the `application` packages.
* The `adapter` package contains logic to connect to external systems and/or infrastructure. No adapter may depend on another adapter. Adapters can use both the items of the `domain` as well as the `application` packages. Vice versa, neither the `domain` nor the `application` packages must contain dependencies on any `adapter` package.
. The `domainModels` packages contain the domain entities.
. The packages in `domainServices` contains services that use the entities in the `domainModel` packages.
* The `applicationServices` packages contain services and configuration to run the application and use cases.
It can use the items of the `domain` package but there must not be any dependency from the `domain`
to the `application` packages.
* The `adapter` package contains logic to connect to external systems and/or infrastructure.
No adapter may depend on another adapter. Adapters can use both the items of the `domain` as well as
the `application` packages. Vice versa, neither the `domain` nor the `application` packages must
contain dependencies on any `adapter` package.


[plantuml, "onion-architecture-check"]
----
skinparam componentStyle uml2
skinparam component {
BorderColor #grey
BackgroundColor #white
}
skinparam class {
BorderColor #grey
BackgroundColor #white
}
package "com.myapp.domain.model" {
class DomainModel
}
package com.myapp.domain.service {
class DomainService
interface DomainRepository
package com.myapp.domain {
package model {
class DomainModel
}
package service {
class DomainService
interface DomainRepository
}
}
package com.myapp.application {
class ApplicationConfiguration
}
package com.myapp.adapter.cli {
class Cli
}
package com.myapp.adapter.persistence {
class NoSqlRepository
}
package com.myapp.adapter.rest {
class RestController
package com.myapp.adapter {
package cli {
class Cli
}
package persistence {
class NoSqlRepository
}
package rest {
class RestController
}
}
DomainService --> DomainModel #green
DomainService --> DomainRepository #green
DomainService -right-> DomainRepository #green
ApplicationConfiguration --> DomainService #green
ApplicationConfiguration ---> DomainService #green
Cli --> DomainService #green
Cli --> DomainRepository #green
NoSqlRepository -up-|> DomainRepository #green
NoSqlRepository --|> DomainRepository #green
NoSqlRepository --> DomainModel #green
RestController --> DomainService #green
RestController -> DomainService #green
RestController --> DomainModel #green
DomainModel -up--> DomainService #crimson
DomainModel ---> DomainService #crimson
note right on link #crimson: the domain model\nmust be independent
DomainService -up--> RestController #crimson
note right on link #crimson: the domain must not know\nabout any adapters
DomainService ---> NoSqlRepository #crimson
note right on link #crimson: the domain must not know about\nany implementation of domain interfaces
DomainService --> RestController #crimson
note right on link #crimson: the domain must not know\nabout any driving adapters
DomainService --> NoSqlRepository #crimson
note right on link #crimson: the domain must not know about\nspecific technical infrastructure
ApplicationConfiguration -up-> Cli
note right on link #crimson: the application must not know\nabout any adapters
ApplicationConfiguration ---> Cli
note right on link #crimson: application services must not\nknow about any adapters
Cli ---> RestController #crimson
Cli --> RestController #crimson
note right on link #crimson: one adapter must not know\nabout any other adapter
----

Expand Down
185 changes: 182 additions & 3 deletions docs/userguide/html/000_Index.html
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ <h1>ArchUnit User Guide</h1>
<li><a href="#_slices">8.2. Slices</a></li>
<li><a href="#_general_coding_rules">8.3. General Coding Rules</a></li>
<li><a href="#_plantuml_component_diagrams_as_rules">8.4. PlantUML Component Diagrams as rules</a></li>
<li><a href="#_freezing_arch_rules">8.5. Freezing Arch Rules</a></li>
</ul>
</li>
<li><a href="#_junit_support">9. JUnit Support</a>
Expand Down Expand Up @@ -1751,9 +1752,71 @@ <h3 id="_architectures"><a class="anchor" href="#_architectures"></a>8.1. Archit
</div>
</div>
<div class="paragraph">
<p>At the moment this only provides a convenient check for a layered architecture (compare
<a href="#_what_to_check">What to Check</a>), but in the future it might be extended for styles like a hexagonal
architecture, pipes and filters, separation of business logic and technical infrastructure, etc.</p>
<p>At the moment this only provides a convenient check for a layered architecture and onion architecture.
But in the future it might be extended for styles like a pipes and filters,
separation of business logic and technical infrastructure, etc.</p>
</div>
<div class="sect3">
<h4 id="_layered_architecture"><a class="anchor" href="#_layered_architecture"></a>8.1.1. Layered Architecture</h4>
<div class="paragraph">
<p>In layered architectures, we define different layers and how those interact with each other.
An example setup for a simple 3-tier architecture can be found in <a href="#_layer_checks">Layer Checks</a>.</p>
</div>
</div>
<div class="sect3">
<h4 id="_onion_architecture"><a class="anchor" href="#_onion_architecture"></a>8.1.2. Onion Architecture</h4>
<div class="paragraph">
<p>In an "Onion Architecture" (also known as "Hexagonal Architecture" or "Ports and Adapters"),
we can define domain packages and adapter packages as follows.</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-java hljs" data-lang="java">onionArchitecture()
.domainModels("com.myapp.domain.model..")
.domainServices("com.myapp.domain.service..")
.applicationServices("com.myapp.application..")
.adapter("cli", "com.myapp.adapter.cli..")
.adapter("persistence", "com.myapp.adapter.persistence..")
.adapter("rest", "com.myapp.adapter.rest..");</code></pre>
</div>
</div>
<div class="paragraph">
<p>The semantic follows the descriptions in <a href="https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/" class="bare">https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/</a>.
More precisely, the following holds:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>The <code>domain</code> package is the core of the application. It consists of two parts.</p>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>The <code>domainModels</code> packages contain the domain entities.</p>
</li>
<li>
<p>The packages in <code>domainServices</code> contains services that use the entities in the <code>domainModel</code> packages.</p>
</li>
</ol>
</div>
</li>
<li>
<p>The <code>applicationServices</code> packages contain services and configuration to run the application and use cases.
It can use the items of the <code>domain</code> package but there must not be any dependency from the <code>domain</code>
to the <code>application</code> packages.</p>
</li>
<li>
<p>The <code>adapter</code> package contains logic to connect to external systems and/or infrastructure.
No adapter may depend on another adapter. Adapters can use both the items of the <code>domain</code> as well as
the <code>application</code> packages. Vice versa, neither the <code>domain</code> nor the <code>application</code> packages must
contain dependencies on any <code>adapter</code> package.</p>
</li>
</ul>
</div>
<div class="imageblock">
<div class="content">
<img src="onion-architecture-check.png" alt="onion architecture check" width="1058" height="743">
</div>
</div>
</div>
</div>
<div class="sect2">
Expand Down Expand Up @@ -1951,6 +2014,122 @@ <h4 id="_configurations"><a class="anchor" href="#_configurations"></a>8.4.1. Co
</div>
</div>
</div>
<div class="sect2">
<h3 id="_freezing_arch_rules"><a class="anchor" href="#_freezing_arch_rules"></a>8.5. Freezing Arch Rules</h3>
<div class="paragraph">
<p>When rules are introduced in grown projects, there are often hundreds or even thousands of violations,
way too many to fix immediately. The only way to tackle such extensive violations is to establish an
iterative approach, which prevents the code base from further deterioration.</p>
</div>
<div class="paragraph">
<p><code>FreezingArchRule</code> can help in these scenarios by recording all existing violations to a <code>ViolationStore</code>.
Consecutive runs will then only report new violations and ignore known violations.
If violations are fixed, <code>FreezingArchRule</code> will automatically reduce the known stored violations to prevent any regression.</p>
</div>
<div class="sect3">
<h4 id="_usage"><a class="anchor" href="#_usage"></a>8.5.1. Usage</h4>
<div class="paragraph">
<p>To freeze an arbitrary <code>ArchRule</code> just wrap it into a <code>FreezingArchRule</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-java hljs" data-lang="java">ArchRule rule = FreezingArchRule.freeze(classes().should()./*complete ArchRule*/);</code></pre>
</div>
</div>
<div class="paragraph">
<p>On the first run all violations of that rule will be stored as the current state. On consecutive runs only
new violations will be reported. By default <code>FreezingArchRule</code> will ignore line numbers, i.e. if a
violation is just shifted to a different line, it will still count as previously recorded
and will not be reported.</p>
</div>
</div>
<div class="sect3">
<h4 id="_configuration"><a class="anchor" href="#_configuration"></a>8.5.2. Configuration</h4>
<div class="paragraph">
<p>By default <code>FreezingArchRule</code> will use a simple <code>ViolationStore</code> based on plain text files.
This is sufficient to add these files to any version control system to continuously track the progress.
You can configure the location of the violation store within <code>archunit.properties</code> (compare <a href="#_advanced_configuration">Advanced Configuration</a>):</p>
</div>
<div class="listingblock">
<div class="title">archunit.properties</div>
<div class="content">
<pre class="highlightjs highlight nowrap"><code>freeze.store.default.path=/some/path/in/a/vcs/repo</code></pre>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_extension"><a class="anchor" href="#_extension"></a>8.5.3. Extension</h4>
<div class="paragraph">
<p><code>FreezingArchRule</code> provides two extension points to adjust the behavior to custom needs.
The first one is the <code>ViolationStore</code>, i.e. the store violations will be recorded to. The second one
is the <code>ViolationLineMatcher</code>, i.e. how <code>FreezingArchRule</code> will associate lines of stored violations
with lines of actual violations. As mentioned, by default this is a line matcher that ignores the
line numbers of violations within the same class.</p>
</div>
<div class="sect4">
<h5 id="_violation_store"><a class="anchor" href="#_violation_store"></a>Violation Store</h5>
<div class="paragraph">
<p>As mentioned in <a href="#_configuration">Configuration</a>, the default <code>ViolationStore</code> is a simple text based store.
It can be exchanged though, for example to store violations in a database.
To provide your own implementation, implement <code>com.tngtech.archunit.library.freeze.ViolationStore</code> and
configure <code>FreezingArchRule</code> to use it. This can either be done programmatically:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-java hljs" data-lang="java">FreezingArchRule.freeze(rule).persistIn(customViolationStore);</code></pre>
</div>
</div>
<div class="paragraph">
<p>Alternatively it can be configured via <code>archunit.properties</code> (compare <a href="#_advanced_configuration">Advanced Configuration</a>):</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code>freeze.store=fully.qualified.name.of.MyCustomViolationStore</code></pre>
</div>
</div>
<div class="paragraph">
<p>You can supply properties to initialize the store by using the namespace <code>freeze.store</code>.
For properties</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code>freeze.store.propOne=valueOne
freeze.store.propTwo=valueTwo</code></pre>
</div>
</div>
<div class="paragraph">
<p>the method <code>ViolationStore.initialize(props)</code> will be called with the properties</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code>propOne=valueOne
propTwo=valueTwo</code></pre>
</div>
</div>
</div>
<div class="sect4">
<h5 id="_violation_line_matcher"><a class="anchor" href="#_violation_line_matcher"></a>Violation Line Matcher</h5>
<div class="paragraph">
<p>The <code>ViolationLineMatcher</code> compares lines from occurred violations with lines from the store.
The default implementation ignores line numbers and counts lines as equivalent when all other details match.
A custom <code>ViolationLineMatcher</code> can again either be defined programmatically:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code class="language-java hljs" data-lang="java">FreezingArchRule.freeze(rule).associateViolationLinesVia(customLineMatcher);</code></pre>
</div>
</div>
<div class="paragraph">
<p>or via <code>archunit.properties</code>:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlightjs highlight nowrap"><code>freeze.lineMatcher=fully.qualified.name.of.MyCustomLineMatcher</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
Expand Down
Binary file added docs/userguide/html/onion-architecture-check.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 223b78d

Please sign in to comment.