From f6d77a684e732d8b28bf3d437011f2f39a71b503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Sat, 18 May 2024 18:28:10 +0300 Subject: [PATCH] docs: update repository --- repository/README.md | 194 +++++++++--------- .../com/iluwatar/repository/AppConfig.java | 2 +- .../iluwatar/repository/AppConfigTest.java | 14 +- 3 files changed, 110 insertions(+), 100 deletions(-) diff --git a/repository/README.md b/repository/README.md index a4cfaae22..471fe8401 100644 --- a/repository/README.md +++ b/repository/README.md @@ -1,39 +1,35 @@ --- title: Repository -category: Architectural +category: Data access language: en tag: - - Data access + - Abstraction + - Data access + - Decoupling + - Persistence --- ## Intent -Repository layer is added between the domain and data mapping layers to isolate domain objects from -details of the database access code and to minimize scattering and duplication of query code. The -Repository pattern is especially useful in systems where number of domain classes is large or heavy -querying is utilized. +To provide a central location for data access logic, abstracting the details of data storage and retrieval from the rest of the application. ## Explanation -Real world example +Real-world example -> Let's say we need a persistent data store for persons. Adding new persons and searching for them -> according to different criteria must be easy. +> Imagine a library system where a librarian acts as the repository. Instead of each library patron searching through the entire library for a book (the data), they go to the librarian (the repository) who knows exactly where each book is located, regardless of whether it's on a shelf, in the storeroom, or borrowed by someone else. The librarian abstracts the complexities of book storage, allowing patrons to request books without needing to understand the storage system. This setup simplifies the process for patrons (clients) and centralizes the management of books (data access logic). In plain words -> Repository architectural pattern creates a uniform layer of data repositories that can be used for -> CRUD operations. +> The Repository pattern provides a central place for handling all data access logic, abstracting the complexities of data storage and retrieval from the rest of the application. [Microsoft documentation](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design) says -> Repositories are classes or components that encapsulate the logic required to access data sources. -> They centralize common data access functionality, providing better maintainability and decoupling -> the infrastructure or technology used to access databases from the domain model layer. +> Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. **Programmatic Example** -Let's first look at the person entity that we need to persist. +Let's first look at the person entity that we need to persist. ```java @@ -45,21 +41,21 @@ Let's first look at the person entity that we need to persist. @NoArgsConstructor public class Person { - @Id - @GeneratedValue - private Long id; - private String name; - private String surname; - private int age; + @Id + @GeneratedValue + private Long id; + private String name; + private String surname; + private int age; - /** - * Constructor. - */ - public Person(String name, String surname, int age) { - this.name = name; - this.surname = surname; - this.age = age; - } + /** + * Constructor. + */ + public Person(String name, String surname, int age) { + this.name = name; + this.surname = surname; + this.age = age; + } } ``` @@ -67,90 +63,84 @@ public class Person { We are using Spring Data to create the `PersonRepository` so it becomes really simple. ```java -@Repository -public interface PersonRepository - extends CrudRepository, JpaSpecificationExecutor { - Person findByName(String name); +@Repository +public interface PersonRepository extends CrudRepository, JpaSpecificationExecutor { + Person findByName(String name); } ``` -Additionally we define a helper class `PersonSpecifications` for specification queries. +Additionally, we define a helper class `PersonSpecifications` for specification queries. ```java public class PersonSpecifications { - public static class AgeBetweenSpec implements Specification { + public static class AgeBetweenSpec implements Specification { - private final int from; + private final int from; - private final int to; + private final int to; - public AgeBetweenSpec(int from, int to) { - this.from = from; - this.to = to; + public AgeBetweenSpec(int from, int to) { + this.from = from; + this.to = to; + } + + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return cb.between(root.get("age"), from, to); + } } - @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { - return cb.between(root.get("age"), from, to); + public static class NameEqualSpec implements Specification { + + public String name; + + public NameEqualSpec(String name) { + this.name = name; + } + + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return cb.equal(root.get("name"), this.name); + } } - - } - - public static class NameEqualSpec implements Specification { - - public String name; - - public NameEqualSpec(String name) { - this.name = name; - } - - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { - return cb.equal(root.get("name"), this.name); - } - } - } ``` And here's the repository example in action. ```java - var peter = new Person("Peter", "Sagan", 17); - var nasta = new Person("Nasta", "Kuzminova", 25); - var john = new Person("John", "lawrence", 35); - var terry = new Person("Terry", "Law", 36); +var peter = new Person("Peter", "Sagan", 17); +var nasta = new Person("Nasta", "Kuzminova", 25); +var john = new Person("John", "lawrence", 35); +var terry = new Person("Terry", "Law", 36); - repository.save(peter); - repository.save(nasta); - repository.save(john); - repository.save(terry); +repository.save(peter); +repository.save(nasta); +repository.save(john); +repository.save(terry); - LOGGER.info("Count Person records: {}", repository.count()); +LOGGER.info("Count Person records: {}",repository.count()); - var persons = (List) repository.findAll(); - persons.stream().map(Person::toString).forEach(LOGGER::info); +var persons = (List) repository.findAll(); +persons.stream().map(Person::toString).forEach(LOGGER::info); - nasta.setName("Barbora"); - nasta.setSurname("Spotakova"); - repository.save(nasta); +nasta.setName("Barbora"); +nasta.setSurname("Spotakova"); +repository.save(nasta); +repository.findById(2L).ifPresent(p ->LOGGER.info("Find by id 2: {}",p)); - repository.findById(2L).ifPresent(p -> LOGGER.info("Find by id 2: {}", p)); - repository.deleteById(2L); +repository.deleteById(2L); +LOGGER.info("Count Person records: {}",repository.count()); - LOGGER.info("Count Person records: {}", repository.count()); +repository.findOne(new PersonSpecifications.NameEqualSpec("John")).ifPresent(p ->LOGGER.info("Find by John is {}",p)); - repository - .findOne(new PersonSpecifications.NameEqualSpec("John")) - .ifPresent(p -> LOGGER.info("Find by John is {}", p)); +persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); +LOGGER.info("Find Person with age between 20,40: "); - persons = repository.findAll(new PersonSpecifications.AgeBetweenSpec(20, 40)); +persons.stream().map(Person::toString).forEach(LOGGER::info); - LOGGER.info("Find Person with age between 20,40: "); - persons.stream().map(Person::toString).forEach(LOGGER::info); - - repository.deleteAll(); +repository.deleteAll(); ``` Program output: @@ -171,25 +161,43 @@ Person [id=4, name=Terry, surname=Law, age=36] ## Class diagram -![alt text](./etc/repository.png "Repository") +![Repository](./etc/repository.png "Repository") ## Applicability -Use the Repository pattern when +* Use when you want to decouple the business logic and data access layers of your application. +* Suitable for scenarios where multiple data sources might be used and the business logic should remain unaware of the data source specifics. +* Ideal for testing purposes as it allows the use of mock repositories. -* The number of domain objects is large. -* You want to avoid duplication of query code. -* You want to keep the database querying code in single place. -* You have multiple data sources. +## Known Uses -## Real world examples +* Spring Data JPA: Provides a repository abstraction over JPA implementations. +* Hibernate: Often used with DAOs that act as repositories for accessing and managing data entities. +* Java EE applications frequently utilize repository patterns to separate business logic from data access code. -* [Spring Data](http://projects.spring.io/spring-data/) +## Consequences + +Benefits: + +* Improves code maintainability and readability by centralizing data access logic. +* Enhances testability by allowing mock implementations of repositories. +* Promotes loose coupling between business logic and data access layers. + +Trade-offs: + +* Introduces additional layers of abstraction which might add complexity. +* Potential performance overhead due to the abstraction layer. + +## Related Patterns + +* [Data Mapper](https://java-design-patterns.com/patterns/data-mapper/): While Repository handles data access, Data Mapper is responsible for transferring data between objects and a database, maintaining the data integrity. +* [Unit of Work](https://java-design-patterns.com/patterns/unit-of-work/): Often used alongside Repository to manage transactions and track changes to the data. ## Credits +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) * [Don’t use DAO, use Repository](http://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/) * [Advanced Spring Data JPA - Specifications and Querydsl](https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/) * [Repository Pattern Benefits and Spring Implementation](https://stackoverflow.com/questions/40068965/repository-pattern-benefits-and-spring-implementation) -* [Patterns of Enterprise Application Architecture](https://www.amazon.com/gp/product/0321127420/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321127420&linkCode=as2&tag=javadesignpat-20&linkId=d9f7d37b032ca6e96253562d075fcc4a) * [Design patterns that I often avoid: Repository pattern](https://www.infoworld.com/article/3117713/design-patterns-that-i-often-avoid-repository-pattern.html) diff --git a/repository/src/main/java/com/iluwatar/repository/AppConfig.java b/repository/src/main/java/com/iluwatar/repository/AppConfig.java index 995c790b6..1ba9d5eaf 100644 --- a/repository/src/main/java/com/iluwatar/repository/AppConfig.java +++ b/repository/src/main/java/com/iluwatar/repository/AppConfig.java @@ -61,7 +61,7 @@ public class AppConfig { } /** - * Factory to create a especific instance of Entity Manager. + * Factory to create a specific instance of Entity Manager. */ @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { diff --git a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java index f0213411f..66c7adc07 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java @@ -60,13 +60,15 @@ class AppConfigTest { @Test @Transactional void testQuery() throws SQLException { - var resultSet = dataSource.getConnection().createStatement().executeQuery("SELECT 1"); - var expected = "1"; - String result = null; - while (resultSet.next()) { - result = resultSet.getString(1); + String expected; + String result; + try (var resultSet = dataSource.getConnection().createStatement().executeQuery("SELECT 1")) { + expected = "1"; + result = null; + while (resultSet.next()) { + result = resultSet.getString(1); + } } assertEquals(expected, result); } - }