diff --git a/service-layer/README.md b/service-layer/README.md index 4e50542c7e81..9b4230314d8f 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -36,6 +36,18 @@ Wikipedia says > Service layer is an architectural pattern, applied within the service-orientation design paradigm, which aims to organize the services, within a service inventory, into a set of logical layers. Services that are categorized into a particular layer share functionality. This helps to reduce the conceptual overhead related to managing the service inventory, as the services belonging to the same layer address a smaller set of activities. +## Application of Layer Supertype Pattern in This Example + +The `Layer Supertype` pattern is used in this implementation to provide a common base class for all entities (`BaseEntity`) and DAOs (`DaoBaseImpl`). This pattern helps reduce redundancy by defining shared properties and methods in a superclass that all child classes can inherit. + +### Layer Supertype in the Entity Layer + +The `BaseEntity` class serves as the common base class for all entities, providing shared attributes like `id`. This ensures consistent handling of these attributes across different entity classes like `Wizard`, `Spellbook`, and `Spell`. + +### Layer Supertype in the DAO Layer + +In the DAO layer, the `DaoBaseImpl` class acts as a common base implementation for all DAO classes, offering shared functionalities such as session handling and basic CRUD operations. By inheriting from `DaoBaseImpl`, specific DAO implementations like `WizardDaoImpl` can focus solely on their unique logic, while reusing common functionality. + ## Programmatic Example of Service Layer Pattern in Java Our Java implementation uses the Service Layer pattern to streamline interactions between data access objects (DAOs) and the business logic, ensuring a clean separation of concerns. @@ -88,32 +100,13 @@ Above the entity layer we have DAOs. For `Wizard` the DAO layer looks as follows ```java public interface WizardDao extends Dao { - - Wizard findByName(String name); + } ``` ```java public class WizardDaoImpl extends DaoBaseImpl implements WizardDao { - - @Override - public Wizard findByName(String name) { - Transaction tx = null; - Wizard result; - try (var session = getSessionFactory().openSession()) { - tx = session.beginTransaction(); - var criteria = session.createCriteria(persistentClass); - criteria.add(Restrictions.eq("name", name)); - result = (Wizard) criteria.uniqueResult(); - tx.commit(); - } catch (Exception e) { - if (tx != null) { - tx.rollback(); - } - throw e; - } - return result; - } + } ``` @@ -379,6 +372,11 @@ Implementing a Service Layer in Java * Enhances testability by isolating business logic. * Improves maintainability and flexibility of enterprise applications. +Using the Layer Supertype pattern in conjunction: + +* Reduces boilerplate code by centralizing common logic. +* Increases consistency across layers, simplifying debugging and enhancement. + Trade-offs: * May introduce additional complexity by adding another layer to the application. @@ -386,6 +384,7 @@ Trade-offs: ## Related Java Design Patterns +* [Layer Supertype](https://martinfowler.com/eaaCatalog/layerSupertype.html): Helps reduce duplication in entities and DAOs by centralizing common logic. * [Facade](https://java-design-patterns.com/patterns/facade/): Simplifies interactions with complex subsystems by providing a unified interface. * [DAO (Data Access Object)](https://java-design-patterns.com/patterns/dao/): Often used together with the Service Layer to handle data persistence. * [MVC (Model-View-Controller)](https://java-design-patterns.com/patterns/model-view-controller/): The Service Layer can be used to encapsulate business logic in the model component. @@ -396,3 +395,4 @@ Trade-offs: * [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) * [Spring in Action](https://amzn.to/4asnpSG) * [Service Layer (Martin Fowler)](http://martinfowler.com/eaaCatalog/serviceLayer.html) +* [Layer Supertype (Martin Fowler)](https://martinfowler.com/eaaCatalog/layerSupertype.html) diff --git a/service-layer/etc/service-layer.urm.puml b/service-layer/etc/service-layer.urm.puml index e663e6c4a678..70da6f0370dc 100644 --- a/service-layer/etc/service-layer.urm.puml +++ b/service-layer/etc/service-layer.urm.puml @@ -22,6 +22,7 @@ package com.iluwatar.servicelayer.common { + findAll() : List {abstract} + merge(E extends BaseEntity) : E extends BaseEntity {abstract} + persist(E extends BaseEntity) {abstract} + + findByName(String) : E extends BaseEntity {abstract} } abstract class DaoBaseImpl { # persistentClass : Class @@ -29,6 +30,7 @@ package com.iluwatar.servicelayer.common { + delete(entity : E extends BaseEntity) + find(id : Long) : E extends BaseEntity + findAll() : List + + findByName(name : String) : E extends BaseEntity # getSessionFactory() : SessionFactory + merge(entity : E extends BaseEntity) : E extends BaseEntity + persist(entity : E extends BaseEntity) @@ -71,11 +73,9 @@ package com.iluwatar.servicelayer.wizard { + toString() : String } interface WizardDao { - + findByName(String) : Wizard {abstract} } class WizardDaoImpl { + WizardDaoImpl() - + findByName(name : String) : Wizard } } package com.iluwatar.servicelayer.spellbook { @@ -98,11 +98,9 @@ package com.iluwatar.servicelayer.spellbook { + toString() : String } interface SpellbookDao { - + findByName(String) : Spellbook {abstract} } class SpellbookDaoImpl { + SpellbookDaoImpl() - + findByName(name : String) : Spellbook } } package com.iluwatar.servicelayer.spell { @@ -121,11 +119,9 @@ package com.iluwatar.servicelayer.spell { + toString() : String } interface SpellDao { - + findByName(String) : Spell {abstract} } class SpellDaoImpl { + SpellDaoImpl() - + findByName(name : String) : Spell } } package com.iluwatar.servicelayer.app { @@ -142,18 +138,18 @@ MagicServiceImpl --> "-spellbookDao" SpellbookDao MagicServiceImpl --> "-spellDao" SpellDao Spellbook --> "-spells" Spell Spellbook --> "-wizards" Wizard -DaoBaseImpl ..|> Dao -MagicServiceImpl ..|> MagicService -Spell --|> BaseEntity -SpellDao --|> Dao -SpellDaoImpl ..|> SpellDao -SpellDaoImpl --|> DaoBaseImpl -Spellbook --|> BaseEntity -SpellbookDao --|> Dao -SpellbookDaoImpl ..|> SpellbookDao -SpellbookDaoImpl --|> DaoBaseImpl -Wizard --|> BaseEntity -WizardDao --|> Dao -WizardDaoImpl ..|> WizardDao -WizardDaoImpl --|> DaoBaseImpl -@enduml \ No newline at end of file +DaoBaseImpl ..|> Dao +MagicServiceImpl ..|> MagicService +Spell --|> BaseEntity +SpellDao --|> Dao +SpellDaoImpl ..|> SpellDao +SpellDaoImpl --|> DaoBaseImpl +Spellbook --|> BaseEntity +SpellbookDao --|> Dao +SpellbookDaoImpl ..|> SpellbookDao +SpellbookDaoImpl --|> DaoBaseImpl +Wizard --|> BaseEntity +WizardDao --|> Dao +WizardDaoImpl ..|> WizardDao +WizardDaoImpl --|> DaoBaseImpl +@enduml diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/Dao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/Dao.java index 55aa01b22e3d..83a4403ed3b4 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/Dao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/Dao.java @@ -42,4 +42,6 @@ public interface Dao { void delete(E entity); List findAll(); + + E findByName(String name); } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java index 9fc0b63927db..3d1a25dbd3e8 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java @@ -74,6 +74,27 @@ public E find(Long id) { } return result; } + @Override + public E findByName(String name) { + Transaction tx = null; + E result; + try (var session = getSessionFactory().openSession()) { + tx = session.beginTransaction(); + CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); + CriteriaQuery builderQuery = criteriaBuilder.createQuery(persistentClass); + Root root = builderQuery.from(persistentClass); + builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name)); + Query query = session.createQuery(builderQuery); + result = query.uniqueResult(); + tx.commit(); + } catch (Exception e) { + if (tx != null) { + tx.rollback(); + } + throw e; + } + return result; + } @Override public void persist(E entity) { diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java index 08dda10486a2..fcd0302126a9 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java @@ -31,6 +31,5 @@ */ public interface SpellDao extends Dao { - Spell findByName(String name); } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java index 5ee39f4be733..9b94ab215d92 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java @@ -37,25 +37,4 @@ */ public class SpellDaoImpl extends DaoBaseImpl implements SpellDao { - @Override - public Spell findByName(String name) { - Transaction tx = null; - Spell result; - try (var session = getSessionFactory().openSession()) { - tx = session.beginTransaction(); - CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); - CriteriaQuery builderQuery = criteriaBuilder.createQuery(Spell.class); - Root root = builderQuery.from(Spell.class); - builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name)); - Query query = session.createQuery(builderQuery); - result = query.uniqueResult(); - tx.commit(); - } catch (Exception e) { - if (tx != null) { - tx.rollback(); - } - throw e; - } - return result; - } } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java index 84fd5dfb756f..753dfd8c1deb 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java @@ -31,6 +31,4 @@ */ public interface SpellbookDao extends Dao { - Spellbook findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java index 463b06cdd369..0767c258ffb4 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java @@ -37,26 +37,4 @@ */ public class SpellbookDaoImpl extends DaoBaseImpl implements SpellbookDao { - @Override - public Spellbook findByName(String name) { - Transaction tx = null; - Spellbook result; - try (var session = getSessionFactory().openSession()) { - tx = session.beginTransaction(); - CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); - CriteriaQuery builderQuery = criteriaBuilder.createQuery(Spellbook.class); - Root root = builderQuery.from(Spellbook.class); - builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name)); - Query query = session.createQuery(builderQuery); - result = query.uniqueResult(); - tx.commit(); - } catch (Exception e) { - if (tx != null) { - tx.rollback(); - } - throw e; - } - return result; - } - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java index 33dd3d3432fb..36d63f3b3c08 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java @@ -31,6 +31,4 @@ */ public interface WizardDao extends Dao { - Wizard findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java index d35c9b0ca21b..33a1aca46888 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java @@ -35,26 +35,4 @@ * WizardDao implementation. */ public class WizardDaoImpl extends DaoBaseImpl implements WizardDao { - - @Override - public Wizard findByName(String name) { - Transaction tx = null; - Wizard result; - try (var session = getSessionFactory().openSession()) { - tx = session.beginTransaction(); - CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); - CriteriaQuery builderQuery = criteriaBuilder.createQuery(Wizard.class); - Root root = builderQuery.from(Wizard.class); - builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name)); - Query query = session.createQuery(builderQuery); - result = query.uniqueResult(); - tx.commit(); - } catch (Exception e) { - if (tx != null) { - tx.rollback(); - } - throw e; - } - return result; - } } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java index c563ead81924..2cfd343c5374 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java @@ -27,10 +27,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.iluwatar.servicelayer.hibernate.HibernateUtil; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import com.iluwatar.servicelayer.spell.Spell; +import org.hibernate.HibernateException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -141,5 +144,51 @@ void testSetName() { assertEquals(expectedName, entity.getName()); assertEquals(expectedName, entity.toString()); } + @Test + void testFindByName() { + final var localDao = getDao(); + final var allEntities = localDao.findAll(); + for (final var entity : allEntities) { + final var entityByName = localDao.findByName(entity.getName()); + assertNotNull(entityByName); + assertEquals(entity.getId(), entityByName.getId()); + assertEquals(entity.getName(), entityByName.getName()); + } + } + @Test + void testPersistException() { + final var faultyDao = new DaoBaseImpl() { + @Override + public void persist(Spell entity) { + throw new HibernateException("Simulated Hibernate exception"); + } + }; + Spell faultyEntity = new Spell(); + assertThrows(HibernateException.class, () -> faultyDao.persist(faultyEntity)); + } + @Test + void testFindException() { + final var faultyDao = new DaoBaseImpl() { + @Override + public Spell find(Long id) { + throw new HibernateException("Simulated Hibernate exception"); + } + }; + + assertThrows(HibernateException.class, () -> faultyDao.find(1L)); + } + + @Test + void testDeleteException() { + final var faultyDao = new DaoBaseImpl() { + @Override + public void delete(Spell entity) { + throw new HibernateException("Simulated Hibernate exception"); + } + }; + + Spell faultyEntity = new Spell(); + assertThrows(HibernateException.class, () -> faultyDao.delete(faultyEntity)); + } } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java index 9b75f531924f..601f4aaa192a 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java @@ -24,11 +24,7 @@ */ package com.iluwatar.servicelayer.spell; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import com.iluwatar.servicelayer.common.BaseDaoTest; -import org.junit.jupiter.api.Test; /** * SpellDaoImplTest @@ -40,16 +36,4 @@ public SpellDaoImplTest() { super(Spell::new, new SpellDaoImpl()); } - @Test - void testFindByName() { - final var dao = getDao(); - final var allSpells = dao.findAll(); - for (final var spell : allSpells) { - final var spellByName = dao.findByName(spell.getName()); - assertNotNull(spellByName); - assertEquals(spell.getId(), spellByName.getId()); - assertEquals(spell.getName(), spellByName.getName()); - } - } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java index 55f3b8b96329..6b7929902227 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java @@ -24,11 +24,7 @@ */ package com.iluwatar.servicelayer.spellbook; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import com.iluwatar.servicelayer.common.BaseDaoTest; -import org.junit.jupiter.api.Test; /** * SpellbookDaoImplTest @@ -40,16 +36,4 @@ public SpellbookDaoImplTest() { super(Spellbook::new, new SpellbookDaoImpl()); } - @Test - void testFindByName() { - final var dao = getDao(); - final var allBooks = dao.findAll(); - for (final var book : allBooks) { - final var spellByName = dao.findByName(book.getName()); - assertNotNull(spellByName); - assertEquals(book.getId(), spellByName.getId()); - assertEquals(book.getName(), spellByName.getName()); - } - } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java index 22a7c6d08e36..50ff19defa2d 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java @@ -24,11 +24,8 @@ */ package com.iluwatar.servicelayer.wizard; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - import com.iluwatar.servicelayer.common.BaseDaoTest; -import org.junit.jupiter.api.Test; + /** * WizardDaoImplTest @@ -40,16 +37,4 @@ public WizardDaoImplTest() { super(Wizard::new, new WizardDaoImpl()); } - @Test - void testFindByName() { - final var dao = getDao(); - final var allWizards = dao.findAll(); - for (final var spell : allWizards) { - final var byName = dao.findByName(spell.getName()); - assertNotNull(byName); - assertEquals(spell.getId(), byName.getId()); - assertEquals(spell.getName(), byName.getName()); - } - } - }