Nested Hexagonal Architecture what's that ?

Introduction:
In this short article we will talk about how to implement a nested hexagonal architecture when your app gets complex.
But first of all if you don't know what hexagonal architecture is feel free to check this article before reading this one.
I will use a diamond printing app to show the nested hexagonal architecture in practice, even though there is code examples in the article i advise to open the source code and take a look at the project structure to fully grasp this article.
What does Nested Hexagonal Architecture even mean ?
it simply means that one or several of your secondary adapters (infrastructure) have become complex enough to require their own hexagon.
Kata Explanation:
Introduction:
To illutstate the concept of nested hexagonal architecture i built a simple CLI application in Java/Spring-Shell that solve this Kata.
I added a little more complexity by printing each line of the diamond in a random color and that's where i built another hexagon in my “CommandLine” adapter who already is an adapter of the principal hexagon of my application.
Principal use case of the kata:
That's a part of the main use case (who is use by the primary adapter which is Spring-Shell) of this kata which build a diamond and print it line by line.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class DisplayDiamond {
public static final String ENTER_A_LETTER = "Enter a letter: ";
public static final String SEPARATOR = " ";
private final CommandLine commandLine;
public DisplayDiamond(CommandLine commandLine) {
this.commandLine = commandLine;
}
public void display() {
try {
String input = commandLine.getInput(ENTER_A_LETTER);
String diamondSideSequence = DiamondSideSequence.generate(input);
int sequenceUniqueLetterLength = Arrays.stream(diamondSideSequence.split(""))
.collect(Collectors.toSet())
.size() ;
commandLine.log("");
logDiamondLines(diamondSideSequence,sequenceUniqueLetterLength,0,0,-1);
}catch (NotLetterException e){
commandLine.log("Please provide a correct single letter");
display();
}
}
}
You certainly noticed that this use case has one dependency which is CommandLine (the secondary adapter of my app) who is used to reading the input from the user and log the lines of the diamond.
Remember that i want to print each line in a random color and it is a concern of the CommandLine adapter.
That's where i build another hexagon who has a use case who is responsible to log each input in a random color with its own dependencies.
Log With Random color use case:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LogWithRandomColor {
public static final int COLOR_CODE_BASE = 30;
public static final int COLOR_NON_INCLUSIVE_LIMIT = 8;
private final Logger logger;
private final RandomNumGenerator randomNumGenerator;
public LogWithRandomColor(Logger logger, RandomNumGenerator randomNumGenerator) {
this.logger = logger;
this.randomNumGenerator = randomNumGenerator;
}
public void log(Object input) {
int colorCode = COLOR_CODE_BASE + randomNumGenerator.generate(COLOR_NON_INCLUSIVE_LIMIT);
logger.log("\u001B[" + colorCode + "m" + input + "\u001B[0m");
}
}
That is the use case who is responsible for logging something to the console in a random color.
You can see that's this use case take a Logger (which is an interface but its implementation use System.out.println) and RandomNumGenerator (which is also an interface but its implementation use the java.util Random class).
1
2
3
4
5
6
7
8
9
10
11
12
package app.netlify.clementgombauld.dimondprinter.adapters.secondary.adapters.secondary;
import app.netlify.clementgombauld.dimondprinter.adapters.secondary.core.domain.Logger;
import org.springframework.stereotype.Component;
@Component
public class AppLogger implements Logger {
@Override
public void log(Object input) {
System.out.println(input);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package app.netlify.clementgombauld.dimondprinter.adapters.secondary.adapters.secondary;
import app.netlify.clementgombauld.dimondprinter.adapters.secondary.core.domain.RandomNumGenerator;
import org.springframework.stereotype.Component;
import java.util.Random;
@Component
public class AppRandomNumGenerator implements RandomNumGenerator {
private static Random random = new Random();
@Override
public int generate(int numberNonInclusiveLimit) {
return random.nextInt(numberNonInclusiveLimit);
}
}
This adatper use case is used in the CommandLine adapter who is the use by the main app use case DisplayDiamond.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
public class SpringShellCommandLine implements CommandLine {
private final LineReader lineReader;
private final LogWithRandomColor logWithRandomColor;
@Autowired
@Lazy
public SpringShellCommandLine(LineReader lineReader,LogWithRandomColor logWithRandomColor) {
this.lineReader = lineReader;
this.logWithRandomColor = logWithRandomColor;
}
@Override
public void log(Object obj) {
logWithRandomColor.log(obj);
}
@Override
public String getInput(String input) {
return lineReader.readLine(input);
}
}
Conclusion:
In this article we saw the concept of nested hexagonal architecture in application, who is useful when you've got a complex adapter to write who is not humble (has logic) and often his own dependencies. That's where the fractal shape of the nested hexagonal architecture shine.
Even though the adapter that we wrote is still very simple, keep in mind that in a complex real world application an adatper will certainly be much more complicated than the one in this article so don't hesitate to build another hexagon within your principal hexagon for that adapter.