docs: update service layer

This commit is contained in:
Ilkka Seppälä
2024-05-20 13:03:23 +03:00
parent 2a789cf65a
commit d656295d3c
6 changed files with 181 additions and 208 deletions
+167 -126
View File
@@ -3,104 +3,78 @@ title: Service Layer
category: Architectural category: Architectural
language: en language: en
tag: tag:
- Data access - API design
- Business
- Decoupling
- Enterprise patterns
- Layered architecture
--- ---
## Also known as
* Application Facade
## Intent ## Intent
Service Layer is an abstraction over domain logic. It defines application's boundary with a layer of services that Encapsulate business logic in a distinct layer to promote separation of concerns and to provide a well-defined API for the presentation layer.
establishes a set of available operations and coordinates the application's response in each operation.
## Explanation ## Explanation
Typically applications require different kinds of interfaces to the data they store and the logic they implement. Real-world example
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
> We are writing an application that tracks wizards, spellbooks and spells. Wizards may have spellbooks and spellbooks > 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).
may have spells.
In plain words 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 Wikipedia says
> Service layer is an architectural pattern, applied within the service-orientation design paradigm, which aims to > 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.
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** **Programmatic Example**
The example application demonstrates interactions between a client `App` and a service `MagicService` that allows 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
interaction between wizards, spellbooks and spells. The service is implemented with 3-layer architecture
(entity, dao, service). (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 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`.
`Wizard` class. Other entities not shown here are `Spellbook` and `Spell`.
```java ```java
@Entity @Entity
@Table(name = "WIZARD") @Table(name = "WIZARD")
@Getter
@Setter
public class Wizard extends BaseEntity { public class Wizard extends BaseEntity {
@Id @Id
@GeneratedValue @GeneratedValue
@Column(name = "WIZARD_ID") @Column(name = "WIZARD_ID")
private Long id; private Long id;
private String name; private String name;
@ManyToMany(cascade = CascadeType.ALL) @ManyToMany(cascade = CascadeType.ALL)
private Set<Spellbook> spellbooks; private Set<Spellbook> spellbooks;
public Wizard() { public Wizard() {
spellbooks = new HashSet<>(); spellbooks = new HashSet<>();
} }
public Wizard(String name) { public Wizard(String name) {
this(); this();
this.name = name; this.name = name;
} }
public Long getId() { public void addSpellbook(Spellbook spellbook) {
return id; spellbook.getWizards().add(this);
} spellbooks.add(spellbook);
}
public void setId(Long id) { @Override
this.id = id; public String toString() {
} return name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Spellbook> getSpellbooks() {
return spellbooks;
}
public void setSpellbooks(Set<Spellbook> spellbooks) {
this.spellbooks = spellbooks;
}
public void addSpellbook(Spellbook spellbook) {
spellbook.getWizards().add(this);
spellbooks.add(spellbook);
}
@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 ```java
public interface WizardDao extends Dao<Wizard> { public interface WizardDao extends Dao<Wizard> {
Wizard findByName(String name); Wizard findByName(String name);
} }
public class WizardDaoImpl extends DaoBaseImpl<Wizard> implements WizardDao { public class WizardDaoImpl extends DaoBaseImpl<Wizard> implements WizardDao {
@Override @Override
public Wizard findByName(String name) { public Wizard findByName(String name) {
Transaction tx = null; Transaction tx = null;
Wizard result; Wizard result;
try (var session = getSessionFactory().openSession()) { try (var session = getSessionFactory().openSession()) {
tx = session.beginTransaction(); tx = session.beginTransaction();
var criteria = session.createCriteria(persistentClass); var criteria = session.createCriteria(persistentClass);
criteria.add(Restrictions.eq("name", name)); criteria.add(Restrictions.eq("name", name));
result = (Wizard) criteria.uniqueResult(); result = (Wizard) criteria.uniqueResult();
tx.commit(); tx.commit();
} catch (Exception e) { } catch (Exception e) {
if (tx != null) { if (tx != null) {
tx.rollback(); tx.rollback();
} }
throw e; 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 ```java
public interface MagicService { public interface MagicService {
List<Wizard> findAllWizards(); List<Wizard> findAllWizards();
List<Spellbook> findAllSpellbooks(); List<Spellbook> findAllSpellbooks();
List<Spell> findAllSpells(); List<Spell> findAllSpells();
List<Wizard> findWizardsWithSpellbook(String name); List<Wizard> findWizardsWithSpellbook(String name);
List<Wizard> findWizardsWithSpell(String name); List<Wizard> findWizardsWithSpell(String name);
} }
public class MagicServiceImpl implements MagicService { public class MagicServiceImpl implements MagicService {
private final WizardDao wizardDao; private final WizardDao wizardDao;
private final SpellbookDao spellbookDao; private final SpellbookDao spellbookDao;
private final SpellDao spellDao; private final SpellDao spellDao;
public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) {
this.wizardDao = wizardDao; this.wizardDao = wizardDao;
this.spellbookDao = spellbookDao; this.spellbookDao = spellbookDao;
this.spellDao = spellDao; this.spellDao = spellDao;
} }
@Override @Override
public List<Wizard> findAllWizards() { public List<Wizard> findAllWizards() {
return wizardDao.findAll(); return wizardDao.findAll();
} }
@Override @Override
public List<Spellbook> findAllSpellbooks() { public List<Spellbook> findAllSpellbooks() {
return spellbookDao.findAll(); return spellbookDao.findAll();
} }
@Override @Override
public List<Spell> findAllSpells() { public List<Spell> findAllSpells() {
return spellDao.findAll(); return spellDao.findAll();
} }
@Override @Override
public List<Wizard> findWizardsWithSpellbook(String name) { public List<Wizard> findWizardsWithSpellbook(String name) {
var spellbook = spellbookDao.findByName(name); var spellbook = spellbookDao.findByName(name);
return new ArrayList<>(spellbook.getWizards()); return new ArrayList<>(spellbook.getWizards());
} }
@Override @Override
public List<Wizard> findWizardsWithSpell(String name) { public List<Wizard> findWizardsWithSpell(String name) {
var spell = spellDao.findByName(name); var spell = spellDao.findByName(name);
var spellbook = spell.getSpellbook(); var spellbook = spell.getSpellbook();
return new ArrayList<>(spellbook.getWizards()); 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())); 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 ## Class diagram
![alt text](./etc/service-layer.png "Service Layer")
![Service Layer](./etc/service-layer.png "Service Layer")
## Applicability ## Applicability
Use the Service Layer pattern when
* You want to encapsulate domain logic under API * Use when you need to separate business logic from presentation logic.
* You need to implement multiple interfaces with common logic and data * 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 ## 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) * [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)
@@ -93,7 +93,7 @@ public abstract class DaoBaseImpl<E extends BaseEntity> implements Dao<E> {
@Override @Override
public E merge(E entity) { public E merge(E entity) {
Transaction tx = null; Transaction tx = null;
E result = null; E result;
try (var session = getSessionFactory().openSession()) { try (var session = getSessionFactory().openSession()) {
tx = session.beginTransaction(); tx = session.beginTransaction();
result = (E) session.merge(entity); result = (E) session.merge(entity);
@@ -33,12 +33,16 @@ import javax.persistence.Id;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
/** /**
* Spell entity. * Spell entity.
*/ */
@Entity @Entity
@Table(name = "SPELL") @Table(name = "SPELL")
@Getter
@Setter
public class Spell extends BaseEntity { public class Spell extends BaseEntity {
private String name; private String name;
@@ -60,30 +64,6 @@ public class Spell extends BaseEntity {
this.name = name; 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 @Override
public String toString() { public String toString() {
return name; return name;
@@ -38,12 +38,16 @@ import javax.persistence.Id;
import javax.persistence.ManyToMany; import javax.persistence.ManyToMany;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
/** /**
* Spellbook entity. * Spellbook entity.
*/ */
@Entity @Entity
@Table(name = "SPELLBOOK") @Table(name = "SPELLBOOK")
@Getter
@Setter
public class Spellbook extends BaseEntity { public class Spellbook extends BaseEntity {
@Id @Id
@@ -69,38 +73,6 @@ public class Spellbook extends BaseEntity {
this.name = name; 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<Wizard> getWizards() {
return wizards;
}
public void setWizards(Set<Wizard> wizards) {
this.wizards = wizards;
}
public Set<Spell> getSpells() {
return spells;
}
public void setSpells(Set<Spell> spells) {
this.spells = spells;
}
public void addSpell(Spell spell) { public void addSpell(Spell spell) {
spell.setSpellbook(this); spell.setSpellbook(this);
spells.add(spell); spells.add(spell);
@@ -35,12 +35,16 @@ import javax.persistence.GeneratedValue;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.ManyToMany; import javax.persistence.ManyToMany;
import javax.persistence.Table; import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
/** /**
* Wizard entity. * Wizard entity.
*/ */
@Entity @Entity
@Table(name = "WIZARD") @Table(name = "WIZARD")
@Getter
@Setter
public class Wizard extends BaseEntity { public class Wizard extends BaseEntity {
@Id @Id
@@ -62,30 +66,6 @@ public class Wizard extends BaseEntity {
this.name = name; 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<Spellbook> getSpellbooks() {
return spellbooks;
}
public void setSpellbooks(Set<Spellbook> spellbooks) {
this.spellbooks = spellbooks;
}
public void addSpellbook(Spellbook spellbook) { public void addSpellbook(Spellbook spellbook) {
spellbook.getWizards().add(this); spellbook.getWizards().add(this);
spellbooks.add(spellbook); spellbooks.add(spellbook);
@@ -122,7 +122,7 @@ class MagicServiceImplTest {
} }
@Test @Test
void testFindWizardsWithSpell() throws Exception { void testFindWizardsWithSpell() {
final var wizards = Set.of( final var wizards = Set.of(
mock(Wizard.class), mock(Wizard.class),
mock(Wizard.class), mock(Wizard.class),