Learn more about the Ports & Adapters design pattern
The discovery of the examples for the Ports & Adapters design pattern is done in a walking skeleton: “A Walking Skeleton is a tiny implementation of the system that performs a small end-to-end function.” The term clearly conveys that the implementation of the example is carried out in steps:
- Step 1 is the hexagon with the business logic: the use cases and their acceptance tests.
- Step 2 is the addition of the driving port adapters. There is only one driving port, but two variants, REST and AMQP, thus two adapters, and for each adapter, the corresponding client library. It also demonstrates how synchronous calls can be emulated using AMQP concepts.
- Step 3 is the addition of the driven port adapters. There is only one driven port, for storing data in a database.
- Step 3 bis, as the wording indicates, is a variant of Step 3. The variant introduces a driven port adapter for sending responses when using AMQP.
Read the source files in your favorite development environment. We favor using Eclipse. Before importing the projects into Eclipse, build them by executing the following command:
Start by looking at the readme.md file located in the root directory of the walking skeleton. Next, browse through the code to build a good overview of the modules: hexagon, driving port adapters, driven port adapters, and their tests, whether they are JUnit unitary tests or JUnit integration tests.
There is one new middleware or software engineering paradigm that
is introduced in Step 3 of the walking
skeleton: Aspect
Oriented Programming, done here with
the Eclipse AspectJ
framework. The role of AspectJ is to wrap all the methods of the
application façade that correspond to use cases with “start
transaction” and “end transaction” statements: See the
specification of the “pointcuts” in
file
Step3_WalkingSkeleton/adapter-driven-port-forstoringfamiliesgifts-orm-jpa-eclipselink/src/main/resources/META-INF/aop.xml
and the Java code that is added in
class
forstoringfamiliesgifts.orm.jpa.eclipselink.AddTransactionToApplicationFacadeAspect.
When you have understood the walking skeleton, you are ready to decompose your own microservices into adapter, startup, and client library modules.
Verify first that you are able to answer the following questions:
-
Which elements (classes, interfaces, etc.) represent the
application’s ports? Where are these elements implemented? How
are they shared between modules? Discuss about the choices
that are made in the walking skeketon. First, search for
the driving port in Step 2; next, search for the driven
port in Step 3.
Ports are APIs, thus Java interfaces.
In Step 2, to find the driving port, we can look at the application façade. Class ApplicationFacade in the hexagon implements the ForManagingFamiliesGifts interface. The latter interface is implemented in a separate package of the hexagon module. This interafce is shared by making the driving port adapter modules having a Maven dependency upon the hexagon module.
In Step 3, to find the driven port, we can look at the constructor of the application façade (or perhaps a setter with dependency injection). The interface for storing data is in a separate module of the hexagon: ForFamiliesGifts. The sharing of the latter interface is done by making the driven port adapter having a Maven dependency upon the hexagon module.
Discussion: There two approaches for the organisation of the code. In the walking skeleton, as proposed by Juan Manuel Garrido de Paz, the APIs and “common types” for the APIs are in the hexagon, and every adapter depends upon the hexagon. If we prefer not sharing “more than the API and its common types”, then we will do it like in the micro-project: a “common API” module shared by all the other modules, driving port adapters depends upon the hexagon (at least for their tests), the hexagon depends upon the “common API” and the driven port adapters, and the driven port adapters depend upon the “common API”. -
How many adapters are programmed for the driven port? Why this
number? In Step 3
Search for the classes that implement the ForStoringFamiliesGifts interface.
The second adapter in the InMemoryFamiliesGiftsRepository class allows you to run tests without a database. -
Draw a diagram with all the elements: hexagon, adapters, and
startup. What about starting the application? How many
configuration are being considered? Discuss about a startup
module to configure and start the microservice. Search
for the shell scripts.
Step 2: In your diagram, check that you draw one driving port with two driving port adapters (not one port per adapter). Then, there are two possible configurations: either a REST access to the façade or an AMQP access to the façade. This is confirmed by looking for the shell scripts that create and run the microservice using a Java command: shell scripts start_adapter.sh in directory /adapter-driving-port-forfamiliesgifts-amqp-rabbitmq and in directory adapter-driving-port-forfamiliesgifts-rest-jaxrs.
Discussion: Another solution would be to have two startup modules, or even to look into injection-based solutions (not studied in this course).
Step 3: a diagram with more ports, etc. -
Which AMQP concepts are brought to play for emulating
synchronous calls to the façade? What is in addition the
impact on the API? Have a look to
the Command and ReturnValue
interfaces.
A correlation identifier is added to the publish of commands and inserted into publish of the response. See Step 6 of the AMQP discovery lab.
- In Step 3, EclipseLinkFamiliesGiftRepository class in module csc5002.ws.s3.adapter-driven-forstoringfamiliesgifts-orm-jpa-eclipselink is worth reading, as there isn't much data persistence in your micro-project.