In this tutorial we will create simple Spring application based on the Hexagonal Architecture (also called as ports and adapters). This concept is focused on separating implementation of business rules from technical details, it is very flexible and doesn’t require specific implementation (like naming of the modules , classes, adapters and so on). You can use this example as template to start with your own application.
Technologies used:
- Spring Boot 2.1.0.RELEASE
- JDK 1.8
- Gradle 4.10.2
1. Project setup
- pets-domain – contains business logic (domain data structures and business processing rules). This module should define and provide primary ports – used for calling business logic (like entry point for this module). All secondary ports, which domain should depend on, must also be defined here, but without the implementation. These ports are used to communicate with the world. Interface of all the ports should depend only on domain model.build.gradle for the module:
dependencies { compile group: 'org.springframework.boot', name: 'spring-boot-starter-web' }
- pets-infrastructure – contains implementation for secondary ports defined in pets-domain module. Implementations of these ports are called secondary adapters – they directly communicate with specific datasources (databases, filesystems, etc), they can also call other applications (REST, SOAP, JMS, etc). In simple words, this module should contain implementation details related with providing data (or with communicating with the world) for the the domain module.build.gradle for the module:
dependencies { compile project(':pets-domain') }
- pets-api – module containing primary adapters (which implement primary ports) – these module translates calls from other systems (other applications or just GUI) to call logic from our domain module.build.gradle for the module:
dependencies { compile project(':pets-domain') compile group: 'org.springframework.boot', name: 'spring-boot-starter-web' }
- pets-app – technical module. It wires all the previous modules and contains configuration for the application
build.gradle for the module:apply plugin: 'application' mainClassName = 'com.devcases.pets.PetsApplication' dependencies { compile project(':pets-api') compile project(':pets-infrastructure') compile group: 'org.springframework.boot', name: 'spring-boot-starter-web' }
plugins {
id "base"
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
description = """pets"""
allprojects {
group = 'com.devcases'
version = '0.0.1-SNAPSHOT'
apply plugin: 'java'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
repositories {
maven { url "http://repo.maven.apache.org/maven2" }
mavenCentral()
}
}
subprojects {
sourceCompatibility = 1.8
targetCompatibility = 1.8
apply plugin: 'io.spring.dependency-management'
dependencyManagement {
dependencies {
dependency 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'
}
}
}
2. Domain Logic
The most interesting module in this concept is the domain module – as mentioned above, it contains only raw business logic, it doesn’t even know about how it is deployed (any spring annotations, etc).
In our example we have one simple model:
package com.devcases.pets;
public class Pet {
private String id;
private String name;
private String species;
public Pet(String id, String name, String species) {
this.id = id;
this.name = name;
this.species = species;
}
public void play() {
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getSpecies() {
return species;
}
}
package com.devcases.pets;
import java.util.List;
public interface PetService {
List getAllPets();
}
package com.devcases.pets;
import java.util.List;
public class PetServiceImpl implements PetService {
private PetRepository repository;
public PetServiceImpl(PetRepository repository) {
this.repository = repository;
}
@Override
public List<Pet> getAllPets() {
//do some logic to retrieve all pets
return repository.getAllPets();
}
}
We see, that the service use the interface PetRepository to obtain pets – in our case this is the secondary port:
package com.devcases.pets;
import java.util.List;
public interface PetRepository {
List getAllPets();
}
3. Infrastructure
package com.devcases.pets;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Component
public class PetRepositoryImpl implements PetRepository {
@Override
public List getAllPets() {
//todo replace source of pets with any real datasource
return new ArrayList<>(Arrays.asList(
new Pet("1", "Daisy", "dog"),
new Pet("2", "Bella", "cat")));
}
}
4. Api module
package com.devcases.pets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/pets")
public class PetController {
@Autowired
private PetService petService;
@RequestMapping(value = "/", method = RequestMethod.GET)
public List getAllPets() {
return petService.getAllPets();
}
}
5. App module
This is the place where all the modules are configured together:
package com.devcases.pets;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PetsApplication {
public static void main(String[] args) {
SpringApplication.run(PetsApplication.class, args);
}
}
package com.devcases.pets.configuration;
import com.devcases.pets.PetRepository;
import com.devcases.pets.PetServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfiguration {
@Bean
public PetServiceImpl getUserService(PetRepository petRepository) {
return new PetServiceImpl(petRepository);
}
}
[
{
"id": "1",
"name": "Daisy",
"species": "dog"
},
{
"id": "2",
"name": "Bella",
"species": "cat"
}
]
The example code you can download from here.