Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/architecture/overview.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
= Amido Stacks - Architecture Overview

video::658523841[vimeo]
9 changes: 9 additions & 0 deletions docs/contents.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
About Stacks:
.......

.Architecture
[%collapsible%open]
====
xref:introduction.adoc[Architecture docs]
====

7 changes: 7 additions & 0 deletions docs/development-patterns/introduction.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
= Development Patterns

The following pages describe in detail how Stacks is developed.

As with any active project third party libraries are depended upon to enhance functionality, standardise approaches across the wider community and to reduce boilerplate code or "re-inventing the wheel"; these pages describe how they are used within the context of Stacks.

In addition to this as part of ongoing refactoring activities, code is often reworked to address performance or readability concerns and this too is outlined.
142 changes: 142 additions & 0 deletions docs/development-patterns/reducing_bean_mapping.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
= Reducing Bean Mapping Boilerplate

== Context
Good programming techniques tell us that we should be following a principle of separation of concerns. This means that as developers we often have to shuffle data from one object to another.

A common example of this is where we have a Data Transfer Object (DTO) passed as a parameter in a REST API call, and we need to copy the data from object-graph to another, for example into a Domain Object for it to be subsequently persisted. The same is required in reverse, when we read an object from the data store we need to convert that object into a DTO that is then passed back to the caller.

This becomes more complex when we have not only single instances of an object, but we have a collection of them.

To be clear, it is bad practice to use the same object (i.e., `Class<?>`) in your code for both of the internal operations (managing the domain) and external operations (receiving or passing data to/from a caller) as it makes future change more complex.

The vanilla Java way to approach this is through overloaded constructors or the use of the builder pattern - both of which can become unwieldy as the number of the properties on the class increases. It also means that specific methods need to be manually written to support single objects or lists/collections of objects.

== Solution
The link:https://mapstruct.org/[MapStruct] open-source project is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach.

The generated mapping code uses plain method invocations and thus is fast, type-safe and easy to understand (this is important, as other mappers that use runtime reflection are "black boxes" that need to be executed in order to be debugged).

In addition, there is full support for Spring auto-wiring and Unit Testing.

MapStruct is an "annotation processor" so please make sure this is switched on for your project. Base on your setup, it may also be necessary to `mvn compile` after creating any new mappers.

== Implementation Examples
MapStruct is capable of working in many ways, although the approach taken in the Stacks project is to follow the pattern whereby it uses interfaces to define mapper objects. With the addition of some mapper utility functions it means that a bean-mapper that supports single objects and collections (in addition to other features such as UUID<->String auto-conversion) can be created very easily.

=== Base Mapper Code
The following generic base mapper interface (this is as show at time of writing for example purposes, refer to GitHub for the latest) is used to define a standard set of mapping functions and lives in the Stacks `core-commons` module.

The generic template placeholder D is for the DTO (external) representation and E is for the Entity (internal) representation. As can be seen, this will provide a number of "out of the box" methods such as `toDto()`, `fromDto()`, `toDtoList()` etc.
[source,java]
----
public interface BaseMapper<D, E> {
D toDto(E entity);

E fromDto(D dto);

void updateFromDto(D dto, @MappingTarget E entity);

void updateFromEntity(E entity, @MappingTarget D dto);

List<D> toDtoList(List<E> list);

List<E> fromDtoList(List<D> list);
}
----
In addition to this, some mapping utility functions are included. Some Stacks modules use a UUID as the external representation of an ID whereas internally it uses a String (for persistence).

The mapping utility class is currently structured as follows. This has simple functions that MapStruct will use whenever it needs to convert between these data types.
[source,java]
----
public class MapperUtils {

private MapperUtils() {
// Utility class
}

public static UUID map(String value) {
return (value != null && !value.trim().isEmpty()) ? fromString(value) : null;
}

public static String map(UUID uuid) {
return uuid != null ? uuid.toString() : null;
}
}
----
=== Project Mapper Code
==== Example 1
Whenever a higher-level module (such as project code) requires a mapper between beans (or object-graphs) the implementation is a simple interface that extends the base mapper and imports (or uses) the mapping utility class shown above.

A simple mapper is as follows. This mapper maps between `MenuDTO` and a `Menu` domain object.

It can be seen that it also "uses" another mapper (so that an entire object hierarchy) can be converted in one go, and also uses the `MapperUtils` class that supports the conversion from UUID to String data types.
[source,java]
----
@Mapper(
componentModel = "spring",
uses = {MapperUtils.class, CategoryMapper.class},
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface MenuMapper extends BaseMapper<MenuDTO, Menu> {}
----
==== Example 2
A more complex mapper is shown below. This mapper maps between a top-level Stacks `CreateMenuRequest` DTO and related domain object called `CreateMenuCommand`. The name of fields differs between these two objects, so it is necessary in this instance to overload the `toDto()` and `fromDto()` methods to tell MapStruct how to map between the fields.
[source,java]
----
@Mapper(
componentModel = "spring",
uses = {},
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface CreateMenuMapper extends BaseMapper<CreateMenuRequest, CreateMenuCommand> {

@Override
@Mapping(source = "restaurantId", target = "tenantId")
CreateMenuRequest toDto(CreateMenuCommand command);

@Override
@Mapping(source = "tenantId", target = "restaurantId")
CreateMenuCommand fromDto(CreateMenuRequest request);
}
----
== Unit Test Code
As previously mentioned, MapStruct has full support for Spring Boot testing frameworks such as JUnit.

To use any mappers that have been created structure your tests as follows:
[source,java]
----
@Tag("Unit")
@SpringBootTest(
classes = {
MenuMapper.class,
MenuMapperImpl.class,
...
})
class DomainToDtoMapperMapstructTest {

@Autowired private MenuMapper menuMapper;

...

@Test
void menuToMenuDto() {

// Given
UUID id = randomUUID();
UUID restaurantId = randomUUID();
...

Menu menu =
new Menu(
id.toString(),
restaurantId.toString(),
...);

// When
MenuDTO menuDTO = menuMapper.toDto(menu);

// Then
assertThat(menuDTO.getId()).isEqualTo(id);
assertThat(menuDTO.getRestaurantId()).isEqualTo(restaurantId);
...
}
}
----
96 changes: 96 additions & 0 deletions docs/development-patterns/reducing_swagger_annotation.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
= Reducing Swagger Annotation Overload

== Context
The API documentation is an essential part of building REST APIs to make the services available to all audience. This documentation should help consumers of the service know which all services are available and its fine details. Also, there should be some simple way to test if the service is up.

SpringDoc simplifies the generation and maintenance of API docs based on the OpenAPI 3 specification for the spring boot applications. The exposed services are bound to change and the documentation needs to be updated as the services change. If this is done manually, then it will become a complex process, and it will be prone to error, especially as the number of REST services increase. This is where swagger helps to automate this documentation process and the consumers of this API would see the output of all of this in the swagger UI (for example, the Swagger API Doc Endpoint at /swagger/index.html).

The proliferation of Swagger annotation's means that there is a lot of duplicated APIResponse annotations that bloat the code and make it difficult to maintain and read.

== Solution
To avoid the code duplication around swagger annotations, we have opted to use our own @interface Java annotation to "carry" these annotations and make them a reusable unit which will minimise the duplicated Swagger annotations from controller methods.

== Implementation Examples
We have defined custom java annotations per CRUD operation to be used by the controller classes. This will promote re-usability of the swagger annotations rather than duplicating the code across multiple controller classes.

*Example of java custom annotation:*

In the below example we have:

Added multiple Swagger REST response annotations to our own annotation
Added the Security Requirement annotation
This is so that we have a single annotation that a developer can use to easily apply all of these Swagger repetitive annotations across multiple classes using just a single annotation per class, therefore fixing the problem being addressed - annotation bloat.

In the below example, we are creating ReadAPIResponses annotation.

[source,java]
----
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ApiResponses({@ApiResponse(
responseCode = "404",
description = "Resource not found",
content = {@Content(
mediaType = "application/json",
schema = @Schema(
implementation = ErrorResponse.class))}),
@ApiResponse(
responseCode = "400",
description = "Bad Request",
content = {@Content(
mediaType = "application/json",
schema = @Schema(
implementation = ErrorResponse.class))}
)})
@SecurityRequirement(name = "bearerAuth")
public @interface ReadAPIResponses {}
----
*Using the custom Annotation @ReadAPIResponses:*

In the below code example, java custom annotation @ReadAPIResponses has been used.

[source,java]
----
@RestController
public class MenuController {

@GetMapping(value = "/{id}")
@Operation(tags = "Menu", summary = "Get a menu", description = "By passing the menu id, ...")
@ReadAPIResponses
ResponseEntity<MenuDTO> getMenu(
@PathVariable(name = "id") UUID id,
@Parameter(hidden = true) @RequestAttribute("CorrelationId") String correlationId) {

// Code here

}
}
----
*Overriding the custom annotations:*

We can override our new custom annotation entries by placing the annotation before the new custom annotation. In the below example, @ApiResponse entry will override the 200 response code in @ReadAPIResponses custom annotation as the @ApiResponse comes before @ReadAPIResponses. We just have to make that the annotations are placed in the right order.

[source,java]
----
@RestController
public class MenuController {

@GetMapping(value = "/{id}")
@Operation(tags = "Menu", summary = "Get a menu", description = "By passing the menu id, ...")
@ApiResponse(
responseCode = "200",
description = "Menu",
content =
@Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = MenuDTO.class)))
@ReadAPIResponses
ResponseEntity<MenuDTO> getMenu(
@PathVariable(name = "id") UUID id,
@Parameter(hidden = true) @RequestAttribute("CorrelationId") String correlationId) {

// Code here

}
}
----
109 changes: 109 additions & 0 deletions docs/development-patterns/seperation_of_concerns.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
= Separation of Concerns

== Context
As the size of an application codebase grows, unless the code is structured in a way that makes it straight-forward for it to be evolved then the chances are that any modifications to be made will take longer and new bugs introduced.

This is for a number of reasons, not limited to: -

. No clear view on which code areas need to be modified to effect a business change
. Difficult to replace components or code units as they are not ring-fenced having clear interfaces
. Uncertainty in how the code being modified is actually invoked and executed
. Code blocks that are repeated in numerous places (and may actually differ ever so slightly)
. Difficulty in testing all paths through the codebase for any given change
All of this increases the cognitive load on the developer making it more likely that the quality of the codebase will probably reduce over time.

== Solution
The solution to the problems mentioned above are to ensure that there is clear separation of concerns.

Separation of concerns is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern. For example the business logic of the application is a concern and the user interface is another concern. Changing the user interface should not require changes to business logic and vice versa.

=== 3-Layer Applications
One common way to manage this in a Spring Boot application is to follow the Controller-Service-Repository pattern. One of the big reasons this pattern is so popular is that it does a great job ensuring separation of concerns.

image::https://stacks.amido.com/img/java_separation_of_concerns.png[Tux,450,350]

The Controller layer, the top layer in the diagram above, is solely responsible for exposing the functionality so that it can be consumed by external consumers (including, perhaps, a UI component). The Repository layer, at the bottom, is responsible for storing and retrieving some set of data. The Service layer in the middle is where all the business logic should go. If the business logic requires fetching/saving data, it use a Repository. If someone wants to access the business logic from the outside world, they go through a Controller to get there. It is of course perfectly acceptable for one service to call another service.

This sets up a pretty simple separation of concerns. If code is related to storage/retrieval, it should go in the Repository. If it's dealing with exposing functionality to external consumers, it goes in the Controller. Anything unique in the business logic would go in the Service layer. The Repository doesn’t care which component is invoking it; it simply does what it is asked. The Service layer doesn’t care how it gets accessed, it just does its work, using a Repository where required. And the Controller is just passing the work down to the Service layer, so it can stay nice and lean.

=== Improved Testing
Where this really starts to pay dividends is in the unit testing philosophy.

By having a clean separation of concerns, it is possible to mock adjacent layers and worry about only testing the concerns of that particular layer. Our Controller tests are only worried about response codes and values, and it's easy to mock the service to trigger those conditions. The Service layer can even be tested as a POJO, and by mocking Repository conditions it's possible to test all the business logic therein without having to worry about going through the controller layer to test it.

== Implementation Examples
Spring Boot is based heavily on the Spring dependency injection framework. Dependency injection (DI) means that when one component requires the use of another then it is provided to it (auto-magically) by an overarching management container that contains references to all available components (that are declared using Java annotations in the codebase and discovered at startup).

This makes it very easy to create Spring components (essentially just Java classes) that provide this separation of concerns. Of course, it's still possible for a rogue developer to break this pattern and put the code in the wrong places, but having this pattern should help to alleviate this.

=== Controller Layer
The `@RestController` annotated class is used to manage the transport-level concerns of the application. As can be seen below, it manages the REST API path (in this example a HTTP GET request available at URI `/thing/{id})` and the shuffling of data from the internal representation (a `Thing` domain object) and its external representation (a `ThingDTO` data transfer object).

It can be seen that a `ThingService` component is "wired" into the controller class, whose responsibility it is to provide any business logic (and/or to call peers (other services) or delegate to lower layers).

The code in the controller layer should be as minimal as possible and simply delegate to a service.

[source,java]
----
@RestController
@RequestMapping("/thing")
public class ThingController {

@Autowired
private ThingService thingService;

@GetMapping("/{id}")
public ResponseEntity<ThingDTO> getThing(@PathVariable("id") UUID id) {

return ResponseEntity.ok(thingService.getThing(id));

}
}
----

=== Service Layer
The `@Service` annotated class is the component where any business logic should be performed. This may include sense-checking values or performing custom logic. By doing these activities here it makes the unit re-usable across the application, and means that should the business logic change then it only needs to be modified in one place, and that one place is very clearly related to the thing (no pun intended) in scope.

It can be seen that a `ThingRepository` component is "wired" into the service class, whose responsibility it is to manage any persistence activities such as reading from or writing to a database. Should the business require data to persisted using a different mechanism (such as to/from a filesystem) then only one repository can be switched out for an alternate (observe that it's an interface in the code).

Note the use of in the example below to perform simple bean mapping between domain objects and DTO classes, and vice versa.

[source,java]
----
@Service
public class ThingService {

@Autowired
private ThingRepository thingRepository;

@Autowired
private ThingMapper thingMapper;

public ThingDTO getThing(UUID id) {

Optional<Thing> optThing = thingRepository.findById(id);

if (optThing.isPresent()) {

// Map between Thing and ThingDTO, probably using MapStruct ...
return thingMapper.toDto(optThing.get());

}

throw new ThingNotFoundException();

}
}
----
=== Repository Layer
As discussed above, the `@Repository` annotated class is the component that manages persistence. Spring Boot provides a number of standard interfaces out-of-the-box (such as the `CrudRepository` shown below) which provide methods such as `findById()`, `findAll()`, `save()`, `deleteById()` and so on.

[source,java]
----
@Repository
public interface ThingRepository extends CrudRepository<Thing, UUID> {

// Add any bespoke CRUD methods here

}
----
Empty file added docs/introduction.adoc
Empty file.
Empty file added docs/quick_start.adoc
Empty file.
Loading