Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layer Supertype Pattern #1302 #3134

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions service-layer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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> {

Wizard findByName(String name);

}
```

```java
public class WizardDaoImpl extends DaoBaseImpl<Wizard> 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;
}

}
```

Expand Down Expand Up @@ -379,13 +372,19 @@ 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.
* Can result in performance overhead due to the extra layer of abstraction.

## 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.
Expand All @@ -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)
38 changes: 17 additions & 21 deletions service-layer/etc/service-layer.urm.puml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ package com.iluwatar.servicelayer.common {
+ findAll() : List<E extends BaseEntity> {abstract}
+ merge(E extends BaseEntity) : E extends BaseEntity {abstract}
+ persist(E extends BaseEntity) {abstract}
+ findByName(String) : E extends BaseEntity {abstract}
}
abstract class DaoBaseImpl<E extends BaseEntity> {
# persistentClass : Class<E extends BaseEntity>
+ DaoBaseImpl<E extends BaseEntity>()
+ delete(entity : E extends BaseEntity)
+ find(id : Long) : E extends BaseEntity
+ findAll() : List<E extends BaseEntity>
+ findByName(name : String) : E extends BaseEntity
# getSessionFactory() : SessionFactory
+ merge(entity : E extends BaseEntity) : E extends BaseEntity
+ persist(entity : E extends BaseEntity)
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ public interface Dao<E extends BaseEntity> {
void delete(E entity);

List<E> findAll();

E findByName(String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<E> builderQuery = criteriaBuilder.createQuery(persistentClass);
Root<E> root = builderQuery.from(persistentClass);
builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name));
Query<E> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@
*/
public interface SpellDao extends Dao<Spell> {

Spell findByName(String name);

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,4 @@
*/
public class SpellDaoImpl extends DaoBaseImpl<Spell> 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<Spell> builderQuery = criteriaBuilder.createQuery(Spell.class);
Root<Spell> root = builderQuery.from(Spell.class);
builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name));
Query<Spell> query = session.createQuery(builderQuery);
result = query.uniqueResult();
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw e;
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,4 @@
*/
public interface SpellbookDao extends Dao<Spellbook> {

Spellbook findByName(String name);

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,4 @@
*/
public class SpellbookDaoImpl extends DaoBaseImpl<Spellbook> 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<Spellbook> builderQuery = criteriaBuilder.createQuery(Spellbook.class);
Root<Spellbook> root = builderQuery.from(Spellbook.class);
builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name));
Query<Spellbook> query = session.createQuery(builderQuery);
result = query.uniqueResult();
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw e;
}
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,4 @@
*/
public interface WizardDao extends Dao<Wizard> {

Wizard findByName(String name);

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,4 @@
* WizardDao implementation.
*/
public class WizardDaoImpl extends DaoBaseImpl<Wizard> 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<Wizard> builderQuery = criteriaBuilder.createQuery(Wizard.class);
Root<Wizard> root = builderQuery.from(Wizard.class);
builderQuery.select(root).where(criteriaBuilder.equal(root.get("name"), name));
Query<Wizard> query = session.createQuery(builderQuery);
result = query.uniqueResult();
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
throw e;
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Spell>() {
@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<Spell>() {
@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<Spell>() {
@Override
public void delete(Spell entity) {
throw new HibernateException("Simulated Hibernate exception");
}
};

Spell faultyEntity = new Spell();
assertThrows(HibernateException.class, () -> faultyDao.delete(faultyEntity));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
}
}

}
Loading
Loading