<?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: Thomas</title>
    <description>The latest articles on DEV Community by Thomas (@the_autistic_programmer).</description>
    <link>https://dev.to/the_autistic_programmer</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%2F582260%2F07cf0063-368a-4571-bcad-0fa6e5dae647.png</url>
      <title>DEV Community: Thomas</title>
      <link>https://dev.to/the_autistic_programmer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/the_autistic_programmer"/>
    <language>en</language>
    <item>
      <title>Managing Persistence and Business Logic in Spring: A Clean and Scalable Approach</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Sat, 08 Mar 2025 16:24:03 +0000</pubDate>
      <link>https://dev.to/the_autistic_programmer/managing-persistence-and-business-logic-in-spring-a-clean-and-scalable-approach-2bhn</link>
      <guid>https://dev.to/the_autistic_programmer/managing-persistence-and-business-logic-in-spring-a-clean-and-scalable-approach-2bhn</guid>
      <description>&lt;p&gt;When it comes to managing data access in Java applications, balancing between clean business logic and efficient persistence can be tricky. One of the most effective ways to achieve this balance in a Spring-based application is by separating your domain logic from your persistence layer. By utilizing the right tools, you can ensure that your code is both maintainable and scalable.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk you through an approach that leverages Spring Data JPA, custom repository implementations, and Specifications to provide clean, flexible data access while keeping business logic separate.&lt;/p&gt;

&lt;p&gt;The Basic Structure: Domain Objects vs. Entities&lt;br&gt;
At the heart of our approach is the distinction between domain objects and entities.&lt;/p&gt;

&lt;p&gt;Domain Objects represent the business logic of your application. These are the objects your services, controllers, and other application layers will interact with.&lt;br&gt;
Entities, on the other hand, are the objects that interact directly with the database. They’re tied to your JPA annotations and represent the structure of the database tables.&lt;br&gt;
For example, we might have a Book domain object and a BookEntity that maps to a book table in the database. The goal is to never expose entities directly in your business logic. Instead, you transform them into domain objects that contain only the necessary business data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define the Repository Interface (Domain Layer)
&lt;/h2&gt;

&lt;p&gt;Start by creating an interface that defines your domain-specific operations. Here’s an example for a Book repository:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public interface Books {&lt;br&gt;
    List&amp;lt;Book&amp;gt; findAll();&lt;br&gt;
    Book save(Book book);&lt;br&gt;
    Book findById(Long id);&lt;br&gt;
    void deleteById(Long id);&lt;br&gt;
    List&amp;lt;Book&amp;gt; findByTitle(String title);&lt;br&gt;
}&lt;/code&gt;&lt;br&gt;
Notice that we’re only working with Book domain objects here. There’s no mention of BookEntity or the database at this stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Implement the Repository (Persistence Layer)
&lt;/h2&gt;

&lt;p&gt;Next, we create a custom repository implementation. This implementation will use Spring Data JPA’s JpaRepository to interact with the database. The repository implementation will map between domain objects and entities as needed.&lt;/p&gt;

&lt;p&gt;`@Repository&lt;br&gt;
public class BookRepositoryImpl implements Books {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private final BookRepository repository;

protected BookRepositoryImpl(BookRepository repository) {
    this.repository = repository;
}

@Override
public List&amp;lt;Book&amp;gt; findAll() {
    return this.repository.findAll().stream()
            .map(BookEntity::toDomain)  // Convert Entity to Domain object
            .collect(Collectors.toList());
}

@Override
public Book save(Book book) {
    BookEntity saved = this.repository.save(BookEntity.create(book.getTitle(), book.getPublishedDate()));
    return saved.toDomain();
}

@Override
public Book findById(Long id) {
    return this.repository.findById(id).map(BookEntity::toDomain)
            .orElseThrow(() -&amp;gt; new BookNotFoundException("Book not found with id: " + id));
}

@Override
public void deleteById(Long id) {
    this.repository.deleteById(id);
}

@Override
public List&amp;lt;Book&amp;gt; findByTitle(String title) {
    Specification&amp;lt;BookEntity&amp;gt; hasTitle = BookSpecification.hasTitle(title);
    return this.repository.findAll(Specification.where(hasTitle)).stream()
            .map(BookEntity::toDomain)
            .collect(Collectors.toList());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}`&lt;br&gt;
Key Points:&lt;br&gt;
BookEntity to Domain Conversion: Each method in the implementation maps entities to domain objects using the toDomain() method.&lt;br&gt;
Custom Query Handling: The findByTitle method demonstrates how you can use a Specification to dynamically build queries without hardcoding them into your repository methods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Extend JpaRepository for Basic CRUD Operations
&lt;/h2&gt;

&lt;p&gt;We don’t have to manually implement basic CRUD operations. By extending JpaRepository, we can let Spring Data JPA handle the heavy lifting for us. This gives us methods like findAll(), save(), findById(), etc., for free.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public interface BookRepository extends JpaRepository&amp;lt;BookEntity, Long&amp;gt;, JpaSpecificationExecutor&amp;lt;BookEntity&amp;gt; { }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By extending JpaSpecificationExecutor, you also get the ability to run dynamic queries using Specification objects—more on this in a moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Working with Specifications
&lt;/h2&gt;

&lt;p&gt;Specifications are part of the JPA Criteria API and allow us to build complex queries in a clean, reusable, and composable way. This is especially useful when you need to write dynamic queries based on various search criteria.&lt;/p&gt;

&lt;p&gt;For example, let’s create a simple specification to search for books by title:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public class BookSpecification {&lt;br&gt;
    static Specification&amp;lt;BookEntity&amp;gt; hasTitle(String title) {&lt;br&gt;
        return (root, query, cb) -&amp;gt; cb.like(root.get("title"), "%" + title + "%");&lt;br&gt;
    }&lt;br&gt;
}&lt;/code&gt;&lt;br&gt;
The beauty of specifications is that you can easily combine multiple conditions to create more complex queries without cluttering up your repository layer. You can also add more methods to BookSpecification for other conditions like author, published date, etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Combining Specifications and Query Execution
&lt;/h2&gt;

&lt;p&gt;Now, let’s see how we combine the specification in the repository implementation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@Override&lt;br&gt;
public List&amp;lt;Book&amp;gt; findByTitle(String title) {&lt;br&gt;
    Specification&amp;lt;BookEntity&amp;gt; hasTitle = BookSpecification.hasTitle(title);&lt;br&gt;
    return this.repository.findAll(Specification.where(hasTitle)).stream()&lt;br&gt;
            .map(BookEntity::toDomain)&lt;br&gt;
            .collect(Collectors.toList());&lt;br&gt;
}&lt;/code&gt;&lt;br&gt;
Here, we’re using the Specification.where() method to apply our title filter and executing the query using findAll(). This approach allows us to compose complex query conditions in a clean and flexible manner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Error Handling and Best Practices
&lt;/h2&gt;

&lt;p&gt;Error handling is an important aspect of any repository layer. In our case, we’re using orElseThrow() to throw an exception when a book is not found by its ID. You might want to create a custom exception (e.g., BookNotFoundException) to provide more context or return a custom error message:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@Override&lt;br&gt;
public Book findById(Long id) {&lt;br&gt;
    return this.repository.findById(id)&lt;br&gt;
            .map(BookEntity::toDomain)&lt;br&gt;
            .orElseThrow(() -&amp;gt; new BookNotFoundException("Book not found with id: " + id));&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By separating the domain objects from the persistence layer, we can keep our business logic clean and decoupled from database concerns. The repository pattern, combined with Spring Data JPA’s JpaRepository and JpaSpecificationExecutor, allows us to manage data access in a flexible and maintainable way.&lt;/p&gt;

&lt;p&gt;The use of Specifications is a powerful tool for writing dynamic, complex queries while maintaining clean and reusable code. With this approach, your data access layer becomes both efficient and scalable, helping you to easily adapt to changing business requirements.&lt;/p&gt;

&lt;p&gt;By following these principles, you’ll be able to create a robust and maintainable Spring application with a clear separation between business logic and persistence concerns.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>encapsulation</category>
      <category>cleancoding</category>
    </item>
    <item>
      <title>Test ApplicationReadyEvent in Spring boot</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Thu, 27 Feb 2025 08:26:47 +0000</pubDate>
      <link>https://dev.to/the_autistic_programmer/test-applicationreadyevent-in-spring-boot-2l3p</link>
      <guid>https://dev.to/the_autistic_programmer/test-applicationreadyevent-in-spring-boot-2l3p</guid>
      <description>&lt;p&gt;I had a migration feature to write and that had to run every time the application started. I will only have to do it once, but idempotent code will be for another time. &lt;/p&gt;

&lt;p&gt;Code that will run on initialization of the application is pretty easy in Spring. Annotate a method in a discoverable bean with &lt;code&gt;@EventListener(ApplicationReadyEvent.class)&lt;/code&gt; and you good to go. &lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
@Component&lt;br&gt;
public class FutonMigrationBean {&lt;br&gt;
    private static boolean migrationFinished = false;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public FutonMigrationBean() {
}

@EventListener(ApplicationReadyEvent.class)
public void migrateFuton() throws Exception {
    //some migration code

    migrationFinished = true;
}

public static boolean isMigrationFinished() {
    return migrationFinished;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;It doesn't have to be for only Application event instances, you can become as creative as you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  But how do we know this code will keep running on startup
&lt;/h2&gt;

&lt;p&gt;We have to write a test that verifies the implementation of the code, but if we require this code to run on startup, we also have be sure that keeps on happening.&lt;/p&gt;

&lt;p&gt;For that we have a spring boot test. In my implementation, I have hidden everything besides the boolean 'migrationFinished'. The boolean is initialized as 'false', so if it's true, we know that the code has ran.&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
@ExtendWith(SpringExtension.class)&lt;br&gt;
@ContextConfiguration(classes = { SpringTestConfiguration.class })&lt;br&gt;
class FutonMigrationOnStartUpTest {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void verifyMigrationOnStartUp() {
    Assertions.assertThat(FutonMigrationBean.isMigrationFinished()).isTrue();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Because it's a little overkill to use springboot test and load the entire context, we limit ourselves to a small config that contains what we need inside &lt;code&gt;SpringTestConfiguration.class&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
@Configuration&lt;br&gt;
@ComponentScan("be.inbo.inboveg.mobile.migration")&lt;br&gt;
public class SpringTestConfiguration {;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Bean(initMethod="migrateFuton")
public FutonMigrationBean futonMigrationBean(){};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;It is a configuration of our Spring context, that's why we need &lt;code&gt;@Configuration&lt;/code&gt; annotation. We provide that bean that we need with the class that has to be executed when initialized. &lt;/p&gt;

&lt;p&gt;Now when we run our test, the migration function will be executed.&lt;/p&gt;

&lt;p&gt;Happy testing&lt;/p&gt;

&lt;p&gt;Side note - this is my first post, if you had doubts, questions, feedback, always welcome&lt;/p&gt;

</description>
      <category>junit5</category>
      <category>spring</category>
      <category>testing</category>
    </item>
    <item>
      <title>docker</title>
      <dc:creator>Thomas</dc:creator>
      <pubDate>Wed, 11 Dec 2024 12:09:45 +0000</pubDate>
      <link>https://dev.to/the_autistic_programmer/docker-1l87</link>
      <guid>https://dev.to/the_autistic_programmer/docker-1l87</guid>
      <description></description>
      <category>docker</category>
      <category>containers</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
