<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: A. S. M. Tarek</title>
    <description>The latest articles on DEV Community by A. S. M. Tarek (@asm_tarek).</description>
    <link>https://dev.to/asm_tarek</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2643625%2F23cbd5e9-4def-44af-b4ee-00d05271a0b7.jpg</url>
      <title>DEV Community: A. S. M. Tarek</title>
      <link>https://dev.to/asm_tarek</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/asm_tarek"/>
    <language>en</language>
    <item>
      <title>Hexagonal Architecture: Building Maintainable and Testable Applications</title>
      <dc:creator>A. S. M. Tarek</dc:creator>
      <pubDate>Thu, 19 Jun 2025 06:46:55 +0000</pubDate>
      <link>https://dev.to/asm_tarek/hexagonal-architecture-building-maintainable-and-testable-applications-1gp9</link>
      <guid>https://dev.to/asm_tarek/hexagonal-architecture-building-maintainable-and-testable-applications-1gp9</guid>
      <description>&lt;p&gt;In today’s world of increasingly complex software systems, designing applications that are modular, testable, and easy to maintain is more crucial than ever. Traditional layered architectures often create tight coupling, making testing painful and evolution risky. Enter Hexagonal Architecture (aka Ports and Adapters)- a pattern that flips dependency management on its head. Created by Alistair Cockburn, it isolates your core business logic from external chaos. Let’s dissect how it works and why it’s a game-changer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Hexagonal Architecture?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hexagonal Architecture is a design approach that organizes an application into a central core surrounded by ports and adapters. The "hexagonal" name comes from visualizing the architecture as a hexagon, where the core business logic sits at the center, and the edges represent interfaces (ports) that connect to external systems via adapters. This pattern aims to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Isolate business logic: Keep the core logic independent of external systems like databases, APIs, or user interfaces.&lt;/li&gt;
&lt;li&gt;Enable flexibility: Make components exchangeable by defining clear boundaries.&lt;/li&gt;
&lt;li&gt;Simplify testing: Enable straightforward mocking of external dependencies during testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture achieves these goals by defining ports (interfaces that specify how the core interacts with the outside world) and adapters (concrete implementations that connect ports to specific technologies or systems).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Concepts: Ports and Adapters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To understand Hexagonal Architecture, let's break down its key components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Core (Business Logic): The core contains the application's business logic or use cases. It represents the "what" of the application—its primary functionality. This layer is technology-agnostic, meaning it doesn't know about databases, APIs, or UI frameworks. The core is written in pure domain terms, focusing on solving the business problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ports: Ports are interfaces that define a contract for how the core communicates with the outside world. They act as gateways, specifying what the core needs or provides without dictating how it’s implemented. Ports come in two flavors:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inbound Ports: Define how external systems can interact with the core. For example, a UserService interface that exposes methods like createUser or getUser.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Outbound Ports: Define what the core needs from external systems. For example, a UserRepository interface that declares methods like saveUser or findUserById.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adapters: Adapters are concrete implementations of ports. They bridge the gap between the core and external systems by translating the port’s interface into specific technologies. Adapters also come in two types:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inbound Adapters: Handle incoming requests from external systems (e.g., a REST API controller or a CLI) and invoke the core through inbound ports.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Outbound Adapters: Implement outbound ports to interact with external systems (e.g., a database, message queue, or third-party API).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By using ports and adapters, the core remains decoupled from external systems, making it easy to swap out one adapter for another (e.g., replacing a MySQL database with MongoDB).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use Hexagonal Architecture?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hexagonal Architecture offers several benefits that make it a compelling choice for modern software systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loose Coupling: The core is isolated from external systems, reducing dependencies and making the system more modular.&lt;/li&gt;
&lt;li&gt;Testability: Ports allow easy mocking of external systems, enabling unit tests to focus on business logic without requiring real databases or APIs.&lt;/li&gt;
&lt;li&gt;Flexibility: Adapters can be swapped or extended without changing the core, supporting multiple technologies or deployment scenarios.&lt;/li&gt;
&lt;li&gt;Maintainability: Clear separation of concerns makes the codebase easier to understand and evolve.&lt;/li&gt;
&lt;li&gt;Scalability: The architecture supports adding new adapters (e.g., new UI or storage systems) with minimal effort.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;A Practical Example: Building a User Management System&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s walk through a simple example of a user management system using Hexagonal Architecture. We’ll implement it in Java, but the concepts apply to any language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; We want to build a system that allows creating and retrieving user information. The system should support multiple persistence mechanisms (e.g., in-memory and database) and expose the functionality via a REST API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Define the Core (Business Logic)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core contains the business logic for managing users. We’ll create a User entity and a use case for handling user operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// User.java
public class User {
    private String id;
    private String name;
    private String email;

    public User(String id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getters and setters
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// UserUseCase.java (Business Logic)
public class UserUseCase {
    private final UserRepository userRepository;

    public UserUseCase(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(String name, String email) {
        String id = UUID.randomUUID().toString();
        User user = new User(id, name, email);
        userRepository.saveUser(user);
        return user;
    }

    public User getUser(String id) {
        return userRepository.findUserById(id)
                .orElseThrow(() -&amp;gt; new RuntimeException("User not found"));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2: Define Ports&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll create two ports: an outbound port for persistence and an inbound port for the use case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// UserRepository.java (Outbound Port)
public interface UserRepository {
    void saveUser(User user);
    Optional&amp;lt;User&amp;gt; findUserById(String id);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// UserService.java (Inbound Port)
public interface UserService {
    User createUser(String name, String email);
    User getUser(String id);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3: Implement Adapters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, we’ll create adapters to connect the ports to specific technologies.&lt;/p&gt;

&lt;p&gt;Outbound Adapter: In-Memory Repository&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// InMemoryUserRepository.java
public class InMemoryUserRepository implements UserRepository {
    private final Map&amp;lt;String, User&amp;gt; users = new HashMap&amp;lt;&amp;gt;();

    @Override
    public void saveUser(User user) {
        users.put(user.getId(), user);
    }

    @Override
    public Optional&amp;lt;User&amp;gt; findUserById(String id) {
        return Optional.ofNullable(users.get(id));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inbound Adapter: REST Controller&lt;/p&gt;

&lt;p&gt;Using a framework like Spring Boot, we can create a REST controller to expose the UserService.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// UserController.java
@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public User createUser(@RequestBody UserRequest request) {
        return userService.createUser(request.getName(), request.getEmail());
    }

    @GetMapping("/{id}")
    public User getUser(@PathVariable String id) {
        return userService.getUser(id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// UserRequest.java
public class UserRequest {
    private String name;
    private String email;

    // Getters and setters
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Wiring It Together&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We can use dependency injection (e.g., Spring) to wire the components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ApplicationConfig.java
@Configuration
public class ApplicationConfig {
    @Bean
    public UserRepository userRepository() {
        return new InMemoryUserRepository();
    }

    @Bean
    public UserService userService(UserRepository userRepository) {
        return new UserUseCase(userRepository);
    }

    @Bean
    public UserController userController(UserService userService) {
        return new UserController(userService);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5: Testing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Since the core is isolated, we can easily test the UserUseCase by mocking the UserRepository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
public void shouldCreateUser() {
    UserRepository mockRepository = mock(UserRepository.class);
    UserUseCase useCase = new UserUseCase(mockRepository);

    User user = useCase.createUser("John Doe", "john@example.com");

    verify(mockRepository).saveUser(user);
    assertNotNull(user.getId());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6: Swapping Adapters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To switch to a database (e.g., MongoDB), we only need to create a new UserRepository implementation without changing the core or inbound adapters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// MongoUserRepository.java
public class MongoUserRepository implements UserRepository {
    private final MongoTemplate mongoTemplate;

    public MongoUserRepository(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    @Override
    public void saveUser(User user) {
        mongoTemplate.save(user);
    }

    @Override
    public Optional&amp;lt;User&amp;gt; findUserById(String id) {
        return Optional.ofNullable(mongoTemplate.findById(id, User.class));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common Use Cases&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hexagonal Architecture shines in scenarios where:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You need to support multiple frontends (e.g., web, mobile, CLI).&lt;/li&gt;
&lt;li&gt;You anticipate changing persistence layers (e.g., from SQL to NoSQL).&lt;/li&gt;
&lt;li&gt;You want to write comprehensive unit tests without external dependencies.&lt;/li&gt;
&lt;li&gt;You’re building microservices that need to integrate with various external systems.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Challenges and Considerations&lt;/strong&gt;&lt;br&gt;
While Hexagonal Architecture offers many benefits, it’s not without challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increased Complexity: Defining ports and adapters adds overhead, which may be overkill for simple applications.&lt;/li&gt;
&lt;li&gt;Learning Curve: Teams unfamiliar with the pattern may need time to adapt.&lt;/li&gt;
&lt;li&gt;Over-Abstraction: Excessive use of interfaces can lead to unnecessary complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To mitigate these, start with a simple implementation and gradually introduce ports and adapters as the application grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Hexagonal Architecture is a robust pattern for building flexible, testable, and maintainable software systems. By isolating business logic and using ports and adapters, developers can create applications that are easy to extend and adapt to changing requirements. Whether you’re building a monolithic application or a microservice, this architecture provides a solid foundation for clean, modular design.&lt;/p&gt;

&lt;p&gt;If you’re looking to improve your codebase’s structure or prepare for future scalability, give Hexagonal Architecture a try. Start small, experiment with a single use case, and watch how it transforms your approach to software design.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Efficient Auditing in Spring Boot with Spring Data Envers</title>
      <dc:creator>A. S. M. Tarek</dc:creator>
      <pubDate>Thu, 02 Jan 2025 07:29:52 +0000</pubDate>
      <link>https://dev.to/asm_tarek/efficient-auditing-in-spring-boot-with-spring-data-envers-2eel</link>
      <guid>https://dev.to/asm_tarek/efficient-auditing-in-spring-boot-with-spring-data-envers-2eel</guid>
      <description>&lt;p&gt;Spring Boot is a powerful framework for building robust, scalable applications. One key requirement for enterprise-level applications is maintaining an audit trail for critical changes. In this article, we'll explore how to implement auditing in Spring Boot using Spring Data Envers, which offers a comprehensive solution for entity versioning and history tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive into the implementation, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A basic understanding of Spring Boot and Spring Data JPA.&lt;/li&gt;
&lt;li&gt;An existing Spring Boot project with Spring Data JPA configured.&lt;/li&gt;
&lt;li&gt;Knowledge of Hibernate Envers (an extension to Hibernate that provides auditing capabilities).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Adding Dependencies
&lt;/h2&gt;

&lt;p&gt;First, include the necessary dependencies in your pom.xml (for Maven users) or build.gradle (for Gradle users):&lt;/p&gt;

&lt;p&gt;Maven&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.hibernate&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;hibernate-envers&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gradle&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;implementation 'org.hibernate:hibernate-envers'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Configuring Envers for Auditing
&lt;/h2&gt;

&lt;p&gt;Next, enable auditing in your application by configuring Spring Data JPA and Envers.&lt;/p&gt;

&lt;p&gt;1) Application Properties: Add the following to your application.properties or application.yml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spring:
 properties:
      hibernate:
        envers:
          autoRegisterListeners: true
          storeDataAtDelete: true
          globalWithModifiedFlag: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;"autoRegisterListeners: true" Automatically registers Envers listeners to track entity changes for auditing purposes.&lt;/li&gt;
&lt;li&gt;"storeDataAtDelete: true" Ensures that the entity's data is saved in the audit history when it is deleted, preserving the deleted record's details.&lt;/li&gt;
&lt;li&gt;"globalWithModifiedFlag: true" Adds a "modified" flag to entities, indicating whether the entity has been modified in any revision, enabling more detailed tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) Enable Auditing: In your @SpringBootApplication class or any configuration class, enable auditing like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Configuration
@EnableJpaAuditing
public class JpaConfig {
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Annotating Entities for Auditing
&lt;/h2&gt;

&lt;p&gt;To enable auditing on your entities, you need to annotate them with @Audited from Hibernate Envers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Entity
@Audited(withModifiedFlag = true)
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String position;

    // Getters and Setters
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This annotation ensures that Envers will track any change to the Employee entity. "withModifiedFlag = true", This flag tracks whether the entity has been modified in any revision, making it easier to determine if an entity was changed in a given version. This is useful for detecting modifications without needing to compare full entity states between revisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Creating an Audit Viewer
&lt;/h2&gt;

&lt;p&gt;Envers creates a versioned history table for every audited entity. You can query this history through the AuditReader API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Autowired
private AuditReader auditReader;

public List&amp;lt;Number&amp;gt; getAuditHistory(Long entityId) {
    return auditReader.getRevisions(Employee.class, entityId);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method retrieves a list of revisions for a given entity, helping you track changes over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Querying Audit History
&lt;/h2&gt;

&lt;p&gt;To retrieve the specific revision details, you can use the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Employee getAuditVersion(Long entityId, int revision) {
    return auditReader.find(Employee.class, entityId, revision);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will fetch the employee entity in a specific revision, showing its state at that point in time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cons of using Envers:
&lt;/h2&gt;

&lt;p&gt;Hibernate Envers does not support asynchronous auditing by default, performing synchronous operations to log entity changes. This can introduce performance issues in high-traffic applications due to additional database writes and reads. Moreover, since each revision is stored in audit tables, the storage requirements grow over time, which can impact the long-term scalability of your system.&lt;/p&gt;

&lt;p&gt;Finally, Implementing auditing with Spring Data Envers in Spring Boot is an effective and straightforward approach for tracking changes to your entities. This solution offers a flexible and robust way to ensure that you meet auditing requirements without much overhead.&lt;/p&gt;

&lt;p&gt;By following these steps, you can maintain a detailed history of entity changes, providing a valuable tool for debugging, compliance, and data integrity.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
