From d656295d3c7b7f66b5c88fa8945a74df22afbc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilkka=20Sepp=C3=A4l=C3=A4?= Date: Mon, 20 May 2024 13:03:23 +0300 Subject: [PATCH] docs: update service layer --- service-layer/README.md | 293 ++++++++++-------- .../servicelayer/common/DaoBaseImpl.java | 2 +- .../iluwatar/servicelayer/spell/Spell.java | 28 +- .../servicelayer/spellbook/Spellbook.java | 36 +-- .../iluwatar/servicelayer/wizard/Wizard.java | 28 +- .../magic/MagicServiceImplTest.java | 2 +- 6 files changed, 181 insertions(+), 208 deletions(-) diff --git a/service-layer/README.md b/service-layer/README.md index aebd980b2..ca6aed5c8 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -3,104 +3,78 @@ title: Service Layer category: Architectural language: en tag: - - Data access + - API design + - Business + - Decoupling + - Enterprise patterns + - Layered architecture --- +## Also known as + +* Application Facade + ## Intent -Service Layer is an abstraction over domain logic. It defines application's boundary with a layer of services that -establishes a set of available operations and coordinates the application's response in each operation. +Encapsulate business logic in a distinct layer to promote separation of concerns and to provide a well-defined API for the presentation layer. ## Explanation -Typically applications require different kinds of interfaces to the data they store and the logic they implement. -Despite their different purposes, these interfaces often need common interactions with the application to access and -manipulate its data and invoke its business logic. Encoding the logic of the interactions separately in each module -causes a lot of duplication. It's better to centralize building the business logic inside single Service Layer to avoid -these pitfalls. - -Real world example +Real-world example -> We are writing an application that tracks wizards, spellbooks and spells. Wizards may have spellbooks and spellbooks -may have spells. +> Consider a large restaurant where orders are taken by waitstaff and then sent to different kitchen sections (e.g., grill, salad, dessert). Each section specializes in a part of the meal, but the waitstaff don't interact directly with the kitchen staff. Instead, all orders go through a head chef who coordinates the workflow. The head chef acts like the service layer, handling the business logic (order coordination) and providing a unified interface for the waitstaff (presentation layer) to interact with the kitchen (data access layer). In plain words -> Service Layer is an abstraction over application's business logic. +> A pattern that encapsulates business logic into a distinct layer to promote separation of concerns and provide a clear API for the presentation layer. 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. +> 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. **Programmatic Example** -The example application demonstrates interactions between a client `App` and a service `MagicService` that allows -interaction between wizards, spellbooks and spells. The service is implemented with 3-layer architecture +The example application demonstrates interactions between a client `App` and a service `MagicService` that allows interaction between wizards, spellbooks and spells. The service is implemented with 3-layer architecture (entity, dao, service). -For this explanation we are looking at one vertical slice of the system. Let's start from the entity layer and look at -`Wizard` class. Other entities not shown here are `Spellbook` and `Spell`. +For this explanation we are looking at one vertical slice of the system. Let's start from the entity layer and look at `Wizard` class. Other entities not shown here are `Spellbook` and `Spell`. ```java + @Entity @Table(name = "WIZARD") +@Getter +@Setter public class Wizard extends BaseEntity { - @Id - @GeneratedValue - @Column(name = "WIZARD_ID") - private Long id; + @Id + @GeneratedValue + @Column(name = "WIZARD_ID") + private Long id; - private String name; + private String name; - @ManyToMany(cascade = CascadeType.ALL) - private Set spellbooks; + @ManyToMany(cascade = CascadeType.ALL) + private Set spellbooks; - public Wizard() { - spellbooks = new HashSet<>(); - } + public Wizard() { + spellbooks = new HashSet<>(); + } - public Wizard(String name) { - this(); - this.name = name; - } + public Wizard(String name) { + this(); + this.name = name; + } - public Long getId() { - return id; - } + public void addSpellbook(Spellbook spellbook) { + spellbook.getWizards().add(this); + spellbooks.add(spellbook); + } - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Set getSpellbooks() { - return spellbooks; - } - - public void setSpellbooks(Set spellbooks) { - this.spellbooks = spellbooks; - } - - public void addSpellbook(Spellbook spellbook) { - spellbook.getWizards().add(this); - spellbooks.add(spellbook); - } - - @Override - public String toString() { - return name; - } + @Override + public String toString() { + return name; + } } ``` @@ -109,29 +83,29 @@ 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); + Wizard findByName(String name); } 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; + @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; } - return result; - } } ``` @@ -140,56 +114,56 @@ Next we can look at the Service Layer, which in our case consists of a single `M ```java public interface MagicService { - List findAllWizards(); + List findAllWizards(); - List findAllSpellbooks(); + List findAllSpellbooks(); - List findAllSpells(); + List findAllSpells(); - List findWizardsWithSpellbook(String name); + List findWizardsWithSpellbook(String name); - List findWizardsWithSpell(String name); + List findWizardsWithSpell(String name); } public class MagicServiceImpl implements MagicService { - private final WizardDao wizardDao; - private final SpellbookDao spellbookDao; - private final SpellDao spellDao; + private final WizardDao wizardDao; + private final SpellbookDao spellbookDao; + private final SpellDao spellDao; - public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { - this.wizardDao = wizardDao; - this.spellbookDao = spellbookDao; - this.spellDao = spellDao; - } + public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { + this.wizardDao = wizardDao; + this.spellbookDao = spellbookDao; + this.spellDao = spellDao; + } - @Override - public List findAllWizards() { - return wizardDao.findAll(); - } + @Override + public List findAllWizards() { + return wizardDao.findAll(); + } - @Override - public List findAllSpellbooks() { - return spellbookDao.findAll(); - } + @Override + public List findAllSpellbooks() { + return spellbookDao.findAll(); + } - @Override - public List findAllSpells() { - return spellDao.findAll(); - } + @Override + public List findAllSpells() { + return spellDao.findAll(); + } - @Override - public List findWizardsWithSpellbook(String name) { - var spellbook = spellbookDao.findByName(name); - return new ArrayList<>(spellbook.getWizards()); - } + @Override + public List findWizardsWithSpellbook(String name) { + var spellbook = spellbookDao.findByName(name); + return new ArrayList<>(spellbook.getWizards()); + } - @Override - public List findWizardsWithSpell(String name) { - var spell = spellDao.findByName(name); - var spellbook = spell.getSpellbook(); - return new ArrayList<>(spellbook.getWizards()); - } + @Override + public List findWizardsWithSpell(String name) { + var spell = spellDao.findByName(name); + var spellbook = spell.getSpellbook(); + return new ArrayList<>(spellbook.getWizards()); + } } ``` @@ -211,17 +185,84 @@ And finally we can show how the client `App` interacts with `MagicService` in th wizardsWithSpell.forEach(w -> LOGGER.info("{} has 'Fireball'", w.getName())); ``` +The program output: + +``` +INFO [2024-05-20 10:00:24,548] com.iluwatar.servicelayer.app.App: Enumerating all wizards +INFO [2024-05-20 10:00:24,550] com.iluwatar.servicelayer.app.App: Aderlard Boud +INFO [2024-05-20 10:00:24,550] com.iluwatar.servicelayer.app.App: Anaxis Bajraktari +INFO [2024-05-20 10:00:24,551] com.iluwatar.servicelayer.app.App: Xuban Munoa +INFO [2024-05-20 10:00:24,551] com.iluwatar.servicelayer.app.App: Blasius Dehooge +INFO [2024-05-20 10:00:24,551] com.iluwatar.servicelayer.app.App: Enumerating all spellbooks +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Book of Orgymon +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Book of Aras +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Book of Kritior +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Book of Tamaex +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Book of Idores +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Book of Opaen +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Book of Kihione +INFO [2024-05-20 10:00:24,554] com.iluwatar.servicelayer.app.App: Enumerating all spells +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Ice dart +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Invisibility +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Stun bolt +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Confusion +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Darkness +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Fireball +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Enchant weapon +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Rock armour +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Light +INFO [2024-05-20 10:00:24,558] com.iluwatar.servicelayer.app.App: Bee swarm +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Haste +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Levitation +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Magic lock +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Summon hell bat +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Water walking +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Magic storm +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Entangle +INFO [2024-05-20 10:00:24,559] com.iluwatar.servicelayer.app.App: Find wizards with spellbook 'Book of Idores' +INFO [2024-05-20 10:00:24,560] com.iluwatar.servicelayer.app.App: Xuban Munoa has 'Book of Idores' +INFO [2024-05-20 10:00:24,560] com.iluwatar.servicelayer.app.App: Find wizards with spell 'Fireball' +INFO [2024-05-20 10:00:24,562] com.iluwatar.servicelayer.app.App: Aderlard Boud has 'Fireball' +``` ## Class diagram -![alt text](./etc/service-layer.png "Service Layer") + +![Service Layer](./etc/service-layer.png "Service Layer") ## Applicability -Use the Service Layer pattern when -* You want to encapsulate domain logic under API -* You need to implement multiple interfaces with common logic and data +* Use when you need to separate business logic from presentation logic. +* Ideal for applications with complex business rules and workflows. +* Suitable for projects requiring a clear API for the presentation layer. + +## Known Uses + +* Java EE applications where Enterprise JavaBeans (EJB) are used to implement the service layer. +* Spring Framework applications using the @Service annotation to denote service layer classes. +* Web applications that need to separate business logic from controller logic. + +## Consequences + +Benefits: + +* Promotes code reuse by encapsulating business logic in one place. +* Enhances testability by isolating business logic. +* Improves maintainability and flexibility of the application. + +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 Patterns + +* [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. ## Credits +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Spring in Action](https://amzn.to/4asnpSG) * [Martin Fowler - Service Layer](http://martinfowler.com/eaaCatalog/serviceLayer.html) -* [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) 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 46981c76d..9fc0b6392 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 @@ -93,7 +93,7 @@ public abstract class DaoBaseImpl implements Dao { @Override public E merge(E entity) { Transaction tx = null; - E result = null; + E result; try (var session = getSessionFactory().openSession()) { tx = session.beginTransaction(); result = (E) session.merge(entity); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java index da9837468..f27215166 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java @@ -33,12 +33,16 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; /** * Spell entity. */ @Entity @Table(name = "SPELL") +@Getter +@Setter public class Spell extends BaseEntity { private String name; @@ -60,30 +64,6 @@ public class Spell extends BaseEntity { this.name = name; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Spellbook getSpellbook() { - return spellbook; - } - - public void setSpellbook(Spellbook spellbook) { - this.spellbook = spellbook; - } - @Override public String toString() { return name; diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java index 996d6018f..d81fc8789 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java @@ -38,12 +38,16 @@ import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; /** * Spellbook entity. */ @Entity @Table(name = "SPELLBOOK") +@Getter +@Setter public class Spellbook extends BaseEntity { @Id @@ -69,38 +73,6 @@ public class Spellbook extends BaseEntity { this.name = name; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Set getWizards() { - return wizards; - } - - public void setWizards(Set wizards) { - this.wizards = wizards; - } - - public Set getSpells() { - return spells; - } - - public void setSpells(Set spells) { - this.spells = spells; - } - public void addSpell(Spell spell) { spell.setSpellbook(this); spells.add(spell); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java index 81db836fc..8b607d76e 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java @@ -35,12 +35,16 @@ import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; +import lombok.Getter; +import lombok.Setter; /** * Wizard entity. */ @Entity @Table(name = "WIZARD") +@Getter +@Setter public class Wizard extends BaseEntity { @Id @@ -62,30 +66,6 @@ public class Wizard extends BaseEntity { this.name = name; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Set getSpellbooks() { - return spellbooks; - } - - public void setSpellbooks(Set spellbooks) { - this.spellbooks = spellbooks; - } - public void addSpellbook(Spellbook spellbook) { spellbook.getWizards().add(this); spellbooks.add(spellbook); diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java index c145f8e5f..722ebfdbd 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java @@ -122,7 +122,7 @@ class MagicServiceImplTest { } @Test - void testFindWizardsWithSpell() throws Exception { + void testFindWizardsWithSpell() { final var wizards = Set.of( mock(Wizard.class), mock(Wizard.class),