Plug-in which helps you implement clean architecture in your application.
- Create your polygon definition
- Share it between projects
- Keep clean-architecture between multiple micro-services 👌
Because you are here I suppose that you would like to keep clean codebase architecture, wouldn't you? I'm here to help you!
To make the magic happen, you need following gradle configuration:
plugins {
id 'io.polygonal' version 'X.Y.Z'
}
Next step is to create polygon.yml
file, and that's it.
The maven configuration looks as standard maven plugin configuration. Currently, the plugin is available only via bintray repository.
<build>
<plugins>
<plugin>
<groupId>io.polygonal</groupId>
<artifactId>polygonal-architecture-maven-plugin</artifactId>
<version>X.Y.Z</version>
<configuration>
<basePackage>org.example</basePackage>
</configuration>
</plugin>
</plugins>
</build>
Let assume that every application is built with blocks. From architecture perspective the very basic block/brick in Java app is a package, right? Now imagine that you are going to build a wall from bricks, the only way that bricks will match with each other is that they have the same shape, and the same amount of match side edges. What if the same rule can be applied to the software? Haven't you seen the projects where packaging looks more like delicious spaghetti than a good peace of software? This kind of freaky packages structure projects are really common. Everybody was there, yep? Let stop this! I'd like to show you easy way to keep your architecture clean as a baby ass.
.
├── 📂 accounts ⬅ this is a domain (polygon)
│ └── ...
| 📂 customers ⬅ this is a domain (polygon)
│ └── ...
| 📂 api ⬅ this is a domain (polygon)
│ └── ...
└── 📄 SpringBootApp.java
Maybe you heard about "hexagonal architecture" , "ports&adapters". Polygon architecture it's almost the same, but with additional rules. Your modules need to match with each other. So what is the "polygon" 🤔? To build something using polygons, every peace should match. Whatever polygons you choose -> they can be triangles, quarters, hexagons, doesn't meter, but they need to match with other polygons. In software development language it means that each domain package (polygon) should have the same kind of shape as other domain packages, and the same amount of entrance/communication points (edges).
Let assume that we have following project structure:
.
├── 📂 users
| ├── 📂 models
| | ├── 📄 User.java
| | 📂 ports
| | ├── 📄 UserFinder.java
│ └── RepositoryUserFinder.java
| 📂 posts
| ├── 📂 models
| | ├── 📄 Post.java
| | 📂 ports
| | ├── 📄 PostPublisher.java
│ └── 📄 RSSPostPublisher.java
└── 📄 AppBootstrap.java
We decided to provide clean architecture here. What rules can be applied?
-
the root domain level may contains only package-private classes
-
the ports domain package should contain only public interfaces
-
the models domain package is allowed to contain only public DTO objects
To make that works you need to define gradle DSL configuration or yml one.
You'd like to keep configurations in YML files? no problem. Polygon definition can be kept in yaml file. Default location is src/main/resources/polygon.yml
:
// polygon.yml
polygon:
public: 0
packagePrivate: -1
types: ['interface', 'class', 'enum']
packages:
models:
required: true
public: -1
packagePrivate: 0
types: ['class']
ports:
public: -1
packagePrivate: -1
types: ['interface']
Element | Description |
---|---|
polygon |
The root element. Required |
polygon.public |
How many public scope objects are allowed. -1 unlimited, 0 not allowed, n n allowed |
polygon.packagePrivate |
How many public scope objects are allowed. -1 unlimited, 0 not allowed, n n allowed |
polygon.protected |
How many public scope objects are allowed. -1 unlimited, 0 not allowed, n n allowed |
polygon.internal |
Kotlin only. How many internal scope object are allowed. -1 unlimited, 0 not allowed, n n allowed |
polygon.types |
What types are allowed. Available values are ['interface', 'class', 'enum', 'abstract class', 'data class', 'open class'] |
polygon.packages |
All packages definitions goes here. |
You can define your architecture also via gradle dsl:
// build.gradle
polygonalArchitecture {
sourcesDir = file('src/main/java')
basePackage = 'org.example'
polygon {
packageDef {
publicScope = 0
packagePrivateScope = -1
types = ['interface', 'class', 'enum']
}
packageDef {
name = 'models'
required = true
publicScope = -1
types = ['class']
}
packageDef {
name = 'ports'
publicScope = -1
types = ['interface']
}
}
}
DSL atribute | Description | Default |
---|---|---|
sourcesDir |
Relative path to the sources. Required | file('src/main/java') or file('src/main/kotlin') |
basePackage |
The level0 / package where polygons are stored. Your polygonal architecture starts here. Required | `` |
strictMode |
If true, only defined packages are allowed. | false |
polygonTemplate |
Template file -> if you'd like to keep polygon definition in .yml file. Required if polygon dsl is not defined | null |
polygon |
Polygon gradle dsl configuration. Required if polygonTemplate is not defined | null |
polygon.packageDef |
The polygon package rules definition. | |
polygon.packageDef.name |
The name of the package. The nested packages like abc.defg are allowed. |
'' , it's root level definition |
polygon.packageDef.required |
If true, definied packages is required inside polygon. | false |
polygon.packageDef.publicScope |
How many public scope objects are allowed. -1 unlimited, 0 not allowed, n n allowed |
0 |
polygon.packageDef.packagePrivateScope |
How many public scope objects are allowed. -1 unlimited, 0 not allowed, n n allowed |
0 (-1 for root level) |
polygon.packageDef.protectedScope |
How many public scope objects are allowed. -1 unlimited, 0 not allowed, n n allowed |
0 |
polygon.packageDef.internalScope |
How many internal scope objects are allowed. -1 unlimited, 0 not allowed, n n allowed |
0 |
polygon.packageDef.types |
What types are allowed. Available values are ['interface', 'class', 'enum', 'abstract class', 'data class', 'open class'] |
['interface', 'class', 'enum', 'abstract class'] |
Mixing of gradle DSL, and yaml configuration is allowed. The one rules here is that gradle DSL has higher precedence than yaml, so you can define the base polygon schema in yaml, and then overwrite some rules by gradle configuration.
Are you looking for more polygon examples? Here you go: polygons gallery
If you'd like to check your polygons explicitly you should use the following task:
$ ./gradlew verifyPolygons
or maven:
$ ./mvnw polygonal:verifyPolygons
In case of any problems plugin will tell you, where is the problem. Example output:
> Task :kotlin-e2e:verifyPolygons
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':verifyPolygons'.
> 0 public scope objects are allowed in 'models' package
- package definition inheritance
- groovy language support
- improve multi-threaded processing for maven
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.