What is Hexagonal Achitecture

90 Views
English
#Hexagonal#Achitecture

What is Hexagonal Achitecture?

Hexagonal architecture was proposed by Alistair Cockburn as an architecture that allows for flexible replacement as long as the internal/external connection standards called ports are met.

 

Pros and Cons of Hexagonal Architecture

pros

  1. Flexibility: Reduced dependencies on external systems or infrastructure, allowing components to be easily replaced or updated.
  2. Testability: The ability to independently test business logic helps improve quality and speed up development.
  3. Maintainability: Responsibilities are separated, making the code easier to understand and modify, and allowing for quicker response to changes.

cons

  1. Complexity: Implementation requires defining ports and implementing adapters, which increases the complexity compared to a simple implementation.
  2. Increased initial development time: Building the architecture initially requires time and effort.

That's all for the obvious explanation.

A hexagon is not a hexagon!

These kinds of various diagrams and similar explanations everywhere confuse us. These hexagonal drawings make it look more complicated. It's not a hexagon! It's completely unrelated.

Now I will explain what hexagonal architecture really is.

Before that, we need to understand what software is.

 

What is software?

In the world we live in now, there are various types of software. There are operating systems like Windows and Mac, and there are games ranging from simple 2D games to complex and flashy 3D games. Vending machines also have software. Even lights and light switches are a kind of program.

What do these various software have in common?

They have the process of Input -> Processing -> Output.

This is the essence of all software. It consists of input, processing, and output.

  • Light
    • Input: Switch
    • Processing: Circuit
    • Output: Light
  • Vending machine
    • Input: Purchase button
    • Processing: Payment processing
    • Output: Beverage dispensing
  • Computer
    • Input: Keyboard, mouse
    • Processing: Games, videos
    • Output: Monitor, speaker

All software has the structure described above. And the elements that connect input, processing, and output to each other are called ports. Things like USB 2.0, USB Type-C, HDMI 2.0. To make it easy to understand, I'll explain using a computer as an example.


Ports are important.

Computers have various ports. These various ports are not for connecting just one single device.

USB 규격 - 커넥터, 전송 속도 (2023년)

You can connect various devices like mice, speakers, microphones, keyboards, etc. to a USB 2.0 port. Although they are different machines, they can be connected because they share and agree on port specifications and manufacture devices according to those port specifications.

Thanks to this, we don't have to disassemble and modify the computer every time we connect a different device to it. If every mouse and keyboard had different connection ports... we'd have to change the main unit to match the ports too.

That would be tremendously inconvenient. Therefore, we establish external and internal communication standards called ports and design and manufacture devices according to those ports.

The fatal attraction of ports Software is exactly the same.

  • By defining port specifications and developing software according to those ports, when you want to replace it with new software, you can develop the new software according to the ports and easily swap it in. (Maintainability, Flexibility)
  • Since the testing standard is the port specification, when you replace it with new software, the testing standards are the same as the previous software. Testing becomes easier. (Testability) Hexagonal architecture is what creates this form in software.

 

Components of Hexagonal Architecture

Hexagonal architecture doesn't have a fixed form. Using its core philosophy, it's modified slightly according to the project's characteristics. The commonly used terminology also varies slightly. This means different terms refer to the same object. If a team uses it, they should agree on terminology before developing.

Components The following 5 components are all there is to hexagonal architecture. Other things are added to these 5 components according to the project's characteristics.

  • Presentation: Endpoints exposed externally for requests, connections, delivery, and communication to the application
  • Inbound Port: Common specification definition that must be followed for requests, connections, delivery, and communication from external software to the application
  • Outbound Port: Common specification definition that must be followed for requests, connections, delivery, and communication from the application to external software
  • Inbound Adapter: Implementation that handles requests, connections, delivery, and communication from external software through Inbound Ports
  • Outbound Adapter: Implementation that handles requests, connections, delivery, and communication to external software through Outbound Ports

 

Processing Flow of Hexagonal Architecture

This is the series of processes from input, processing, to output in hexagonal architecture.

 

Presentation Layer

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    @PostMapping
    public ResponseEntity<UserDto> createUser(@RequestBody CreateUserRequest request) {
        User user = userService.createUser(request.getName(), request.getEmail());
        return ResponseEntity.ok(UserDto.from(user));
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
        User user = userService.findById(id);
        return ResponseEntity.ok(UserDto.from(user));
    }
}

Presentation, that is, the part exposed to the outside. External entities make requests to this exposed part.

 

Inbound Port

public interface UserService {
    User createUser(String name, String email);
    User findById(Long id);
    void deleteUser(Long id);
}
public class User {
    private Long id;
    private String name;
    private String email;
    private LocalDateTime createdAt;
    
    public User(String name, String email) {
        this.name = name;
        this.email = email;
        this.createdAt = LocalDateTime.now();
    }
}


This is a form for user-related operations. For the createUser operation, name and email are required and it returns a User. No matter how you implement the interface, the operation must work correctly. The Presentation example operates as a Controller that receives Restful API, but whether it receives through gRPC, Kafka Consume, or TCP Socket communication, calling the function through that interface guarantees the same operation. 😋

 

Inbound Adapter

@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    @Override
    public User createUser(String name, String email) {
        // 비즈니스 로직
        if (userRepository.existsByEmail(email)) {
            throw new DuplicateEmailException("Email already exists");
        }
        
        User user = new User(name, email);
        User savedUser = userRepository.save(user);
        
        // 이메일 발송
        emailService.sendWelcomeEmail(savedUser.getEmail());
        
        return savedUser;
    }
    
    @Override
    public User findById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("User not found"));
    }
    
    @Override
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

Inbound Adapter is an implementation of Inbound Port. Here, UserRepository and EmailService are Outbound Ports.

 

Outbound Port

public interface UserRepository {
    User save(User user);
    Optional<User> findById(Long id);
    boolean existsByEmail(String email);
    void deleteById(Long id);
}

public interface EmailService {
    void sendWelcomeEmail(String email);
    void sendPasswordResetEmail(String email, String token);
}

If Inbound Port is an interface for receiving requests from outside to the application, then Outbound Port is an interface for the application to make requests to the outside. It's easy to understand if you think of it as an interface for external software to receive requests from the application. (The actual operation is slightly different)

 

Outbound Adapter

@Repository
public class JpaUserRepository implements UserRepository {
    
    private final UserJpaRepository jpaRepository;
    
    @Override
    public User save(User user) {
        UserEntity entity = UserEntity.from(user);
        UserEntity saved = jpaRepository.save(entity);
        return saved.toDomain();
    }
    
    @Override
    public Optional<User> findById(Long id) {
        return jpaRepository.findById(id)
            .map(UserEntity::toDomain);
    }
    
    @Override
    public boolean existsByEmail(String email) {
        return jpaRepository.existsByEmail(email);
    }
    
    @Override
    public void deleteById(Long id) {
        jpaRepository.deleteById(id);
    }
}

@Component
public class SmtpEmailService implements EmailService {
    
    private final JavaMailSender mailSender;
    
    @Override
    public void sendWelcomeEmail(String email) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(email);
        message.setSubject("Welcome!");
        message.setText("Welcome to our service!");
        
        mailSender.send(message);
    }
    
    @Override
    public void sendPasswordResetEmail(String email, String token) {
        // 비밀번호 재설정 이메일 로직
    }
}

Outbound Adapter is an implementation that implements Outbound Port. It actually implements request logic suitable for each external software to operate.


Hexagonal architecture is actually simple.

This is all there is to it.

No matter what method is used to make requests, it can handle them all consistently.

No matter what DB you connect to, you can change the data that was retrieved from the DB to API and retrieve data through API instead.

Without changing the Business Logic! (This is very important)

Hexagonal architecture:

  1. Has very strong resistance to environmental changes. (Hexagonal architecture separates and isolates the actual decision-making logic at the innermost part so that it is not affected by the external environment.)
  2. Since Business Logic is completely isolated, it is very suitable for writing Unit Tests, which are completely isolated tests.

 

The essence of hexagonal architecture

The Essence of Hexagonal Architecture Port and Adapter are just terminology, and everything explained above is one of many methods.

There are no absolutes.

The essence of hexagonal architecture can be said to be making logic that is truly important, needs to be meticulously tested, and must be accurate, strong and flexible without being affected by the external environment.

Usually when people think of hexagonal architecture, they tend to think of hexagons, but it's actually not related to hexagons.

What's really important is thoroughly separating what shouldn't change from what can be changed.

If you found this interesting, please give it a like!

Related Posts