mirror of
https://github.com/tiennm99/java-design-patterns.git
synced 2026-05-14 10:58:42 +00:00
399 lines
16 KiB
Markdown
399 lines
16 KiB
Markdown
---
|
|
title: "Service Layer Pattern in Java: Enhancing Application Architecture with Robust Service Layers"
|
|
shortTitle: Service Layer
|
|
description: "Explore the Service Layer pattern for Java applications, a key design solution for separating business logic from presentation logic. Learn its uses, benefits, and implementation with real-world examples and class diagrams to optimize your architectural strategies."
|
|
category: Architectural
|
|
language: en
|
|
tag:
|
|
- API design
|
|
- Business
|
|
- Decoupling
|
|
- Enterprise patterns
|
|
- Layered architecture
|
|
---
|
|
|
|
## Also known as
|
|
|
|
* Application Facade
|
|
|
|
## Intent of Service Layer Design Pattern
|
|
|
|
The Service Layer pattern is crucial for Java developers focusing on building robust application architectures that separate business processes from user interface concerns.
|
|
|
|
The pattern encapsulate business logic in a distinct layer to promote separation of concerns and to provide a well-defined API for the presentation layer.
|
|
|
|
## Detailed Explanation of Service Layer Pattern with Real-World Examples
|
|
|
|
Real-world example
|
|
|
|
> Imagine a complex restaurant system where orders are managed through a centralized 'service layer' to ensure efficient operation and clear communication between the front and back of the house. 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
|
|
|
|
> 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.
|
|
|
|
Architecture diagram
|
|
|
|

|
|
|
|
## 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.
|
|
|
|
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`.
|
|
|
|
```java
|
|
|
|
@Entity
|
|
@Table(name = "WIZARD")
|
|
@Getter
|
|
@Setter
|
|
public class Wizard extends BaseEntity {
|
|
|
|
@Id
|
|
@GeneratedValue
|
|
@Column(name = "WIZARD_ID")
|
|
private Long id;
|
|
|
|
private String name;
|
|
|
|
@ManyToMany(cascade = CascadeType.ALL)
|
|
private Set<Spellbook> spellbooks;
|
|
|
|
public Wizard() {
|
|
spellbooks = new HashSet<>();
|
|
}
|
|
|
|
public Wizard(String name) {
|
|
this();
|
|
this.name = name;
|
|
}
|
|
|
|
public void addSpellbook(Spellbook spellbook) {
|
|
spellbook.getWizards().add(this);
|
|
spellbooks.add(spellbook);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return name;
|
|
}
|
|
}
|
|
```
|
|
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
Next we can look at the Service Layer, which in our case consists of a single `MagicService`.
|
|
|
|
```java
|
|
public interface MagicService {
|
|
|
|
List<Wizard> findAllWizards();
|
|
|
|
List<Spellbook> findAllSpellbooks();
|
|
|
|
List<Spell> findAllSpells();
|
|
|
|
List<Wizard> findWizardsWithSpellbook(String name);
|
|
|
|
List<Wizard> findWizardsWithSpell(String name);
|
|
}
|
|
|
|
public class MagicServiceImpl implements MagicService {
|
|
|
|
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;
|
|
}
|
|
|
|
@Override
|
|
public List<Wizard> findAllWizards() {
|
|
return wizardDao.findAll();
|
|
}
|
|
|
|
@Override
|
|
public List<Spellbook> findAllSpellbooks() {
|
|
return spellbookDao.findAll();
|
|
}
|
|
|
|
@Override
|
|
public List<Spell> findAllSpells() {
|
|
return spellDao.findAll();
|
|
}
|
|
|
|
@Override
|
|
public List<Wizard> findWizardsWithSpellbook(String name) {
|
|
var spellbook = spellbookDao.findByName(name);
|
|
return new ArrayList<>(spellbook.getWizards());
|
|
}
|
|
|
|
@Override
|
|
public List<Wizard> findWizardsWithSpell(String name) {
|
|
var spell = spellDao.findByName(name);
|
|
var spellbook = spell.getSpellbook();
|
|
return new ArrayList<>(spellbook.getWizards());
|
|
}
|
|
}
|
|
```
|
|
|
|
And finally, we can show how the client `App` interacts with `MagicService` in the Service Layer.
|
|
|
|
```java
|
|
@Slf4j
|
|
public class App {
|
|
|
|
public static final String BOOK_OF_IDORES = "Book of Idores";
|
|
|
|
public static void main(String[] args) {
|
|
// populate the in-memory database
|
|
initData();
|
|
// query the data using the service
|
|
queryData();
|
|
}
|
|
|
|
public static void initData() {
|
|
// spells
|
|
var spell1 = new Spell("Ice dart");
|
|
var spell2 = new Spell("Invisibility");
|
|
var spell3 = new Spell("Stun bolt");
|
|
var spell4 = new Spell("Confusion");
|
|
var spell5 = new Spell("Darkness");
|
|
var spell6 = new Spell("Fireball");
|
|
var spell7 = new Spell("Enchant weapon");
|
|
var spell8 = new Spell("Rock armour");
|
|
var spell9 = new Spell("Light");
|
|
var spell10 = new Spell("Bee swarm");
|
|
var spell11 = new Spell("Haste");
|
|
var spell12 = new Spell("Levitation");
|
|
var spell13 = new Spell("Magic lock");
|
|
var spell14 = new Spell("Summon hell bat");
|
|
var spell15 = new Spell("Water walking");
|
|
var spell16 = new Spell("Magic storm");
|
|
var spell17 = new Spell("Entangle");
|
|
var spellDao = new SpellDaoImpl();
|
|
spellDao.persist(spell1);
|
|
spellDao.persist(spell2);
|
|
spellDao.persist(spell3);
|
|
spellDao.persist(spell4);
|
|
spellDao.persist(spell5);
|
|
spellDao.persist(spell6);
|
|
spellDao.persist(spell7);
|
|
spellDao.persist(spell8);
|
|
spellDao.persist(spell9);
|
|
spellDao.persist(spell10);
|
|
spellDao.persist(spell11);
|
|
spellDao.persist(spell12);
|
|
spellDao.persist(spell13);
|
|
spellDao.persist(spell14);
|
|
spellDao.persist(spell15);
|
|
spellDao.persist(spell16);
|
|
spellDao.persist(spell17);
|
|
|
|
// spellbooks
|
|
var spellbookDao = new SpellbookDaoImpl();
|
|
var spellbook1 = new Spellbook("Book of Orgymon");
|
|
spellbookDao.persist(spellbook1);
|
|
spellbook1.addSpell(spell1);
|
|
spellbook1.addSpell(spell2);
|
|
spellbook1.addSpell(spell3);
|
|
spellbook1.addSpell(spell4);
|
|
spellbookDao.merge(spellbook1);
|
|
var spellbook2 = new Spellbook("Book of Aras");
|
|
spellbookDao.persist(spellbook2);
|
|
spellbook2.addSpell(spell5);
|
|
spellbook2.addSpell(spell6);
|
|
spellbookDao.merge(spellbook2);
|
|
var spellbook3 = new Spellbook("Book of Kritior");
|
|
spellbookDao.persist(spellbook3);
|
|
spellbook3.addSpell(spell7);
|
|
spellbook3.addSpell(spell8);
|
|
spellbook3.addSpell(spell9);
|
|
spellbookDao.merge(spellbook3);
|
|
var spellbook4 = new Spellbook("Book of Tamaex");
|
|
spellbookDao.persist(spellbook4);
|
|
spellbook4.addSpell(spell10);
|
|
spellbook4.addSpell(spell11);
|
|
spellbook4.addSpell(spell12);
|
|
spellbookDao.merge(spellbook4);
|
|
var spellbook5 = new Spellbook(BOOK_OF_IDORES);
|
|
spellbookDao.persist(spellbook5);
|
|
spellbook5.addSpell(spell13);
|
|
spellbookDao.merge(spellbook5);
|
|
var spellbook6 = new Spellbook("Book of Opaen");
|
|
spellbookDao.persist(spellbook6);
|
|
spellbook6.addSpell(spell14);
|
|
spellbook6.addSpell(spell15);
|
|
spellbookDao.merge(spellbook6);
|
|
var spellbook7 = new Spellbook("Book of Kihione");
|
|
spellbookDao.persist(spellbook7);
|
|
spellbook7.addSpell(spell16);
|
|
spellbook7.addSpell(spell17);
|
|
spellbookDao.merge(spellbook7);
|
|
|
|
// wizards
|
|
var wizardDao = new WizardDaoImpl();
|
|
var wizard1 = new Wizard("Aderlard Boud");
|
|
wizardDao.persist(wizard1);
|
|
wizard1.addSpellbook(spellbookDao.findByName("Book of Orgymon"));
|
|
wizard1.addSpellbook(spellbookDao.findByName("Book of Aras"));
|
|
wizardDao.merge(wizard1);
|
|
var wizard2 = new Wizard("Anaxis Bajraktari");
|
|
wizardDao.persist(wizard2);
|
|
wizard2.addSpellbook(spellbookDao.findByName("Book of Kritior"));
|
|
wizard2.addSpellbook(spellbookDao.findByName("Book of Tamaex"));
|
|
wizardDao.merge(wizard2);
|
|
var wizard3 = new Wizard("Xuban Munoa");
|
|
wizardDao.persist(wizard3);
|
|
wizard3.addSpellbook(spellbookDao.findByName(BOOK_OF_IDORES));
|
|
wizard3.addSpellbook(spellbookDao.findByName("Book of Opaen"));
|
|
wizardDao.merge(wizard3);
|
|
var wizard4 = new Wizard("Blasius Dehooge");
|
|
wizardDao.persist(wizard4);
|
|
wizard4.addSpellbook(spellbookDao.findByName("Book of Kihione"));
|
|
wizardDao.merge(wizard4);
|
|
}
|
|
|
|
public static void queryData() {
|
|
var wizardDao = new WizardDaoImpl();
|
|
var spellbookDao = new SpellbookDaoImpl();
|
|
var spellDao = new SpellDaoImpl();
|
|
var service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao);
|
|
LOGGER.info("Enumerating all wizards");
|
|
service.findAllWizards().stream().map(Wizard::getName).forEach(LOGGER::info);
|
|
LOGGER.info("Enumerating all spellbooks");
|
|
service.findAllSpellbooks().stream().map(Spellbook::getName).forEach(LOGGER::info);
|
|
LOGGER.info("Enumerating all spells");
|
|
service.findAllSpells().stream().map(Spell::getName).forEach(LOGGER::info);
|
|
LOGGER.info("Find wizards with spellbook 'Book of Idores'");
|
|
var wizardsWithSpellbook = service.findWizardsWithSpellbook(BOOK_OF_IDORES);
|
|
wizardsWithSpellbook.forEach(w -> LOGGER.info("{} has 'Book of Idores'", w.getName()));
|
|
LOGGER.info("Find wizards with spell 'Fireball'");
|
|
var wizardsWithSpell = service.findWizardsWithSpell("Fireball");
|
|
wizardsWithSpell.forEach(w -> LOGGER.info("{} has 'Fireball'", w.getName()));
|
|
}
|
|
}
|
|
```
|
|
|
|
The program output:
|
|
|
|
```
|
|
INFO [2024-05-27 09:16:40,668] com.iluwatar.servicelayer.app.App: Enumerating all wizards
|
|
INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Aderlard Boud
|
|
INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Anaxis Bajraktari
|
|
INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Xuban Munoa
|
|
INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Blasius Dehooge
|
|
INFO [2024-05-27 09:16:40,671] com.iluwatar.servicelayer.app.App: Enumerating all spellbooks
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Orgymon
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Aras
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Kritior
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Tamaex
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Idores
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Opaen
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Book of Kihione
|
|
INFO [2024-05-27 09:16:40,675] com.iluwatar.servicelayer.app.App: Enumerating all spells
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Ice dart
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Invisibility
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Stun bolt
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Confusion
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Darkness
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Fireball
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Enchant weapon
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Rock armour
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Light
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Bee swarm
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Haste
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Levitation
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Magic lock
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Summon hell bat
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Water walking
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Magic storm
|
|
INFO [2024-05-27 09:16:40,679] com.iluwatar.servicelayer.app.App: Entangle
|
|
INFO [2024-05-27 09:16:40,680] com.iluwatar.servicelayer.app.App: Find wizards with spellbook 'Book of Idores'
|
|
INFO [2024-05-27 09:16:40,680] com.iluwatar.servicelayer.app.App: Xuban Munoa has 'Book of Idores'
|
|
INFO [2024-05-27 09:16:40,681] com.iluwatar.servicelayer.app.App: Find wizards with spell 'Fireball'
|
|
INFO [2024-05-27 09:16:40,683] com.iluwatar.servicelayer.app.App: Aderlard Boud has 'Fireball'
|
|
```
|
|
|
|
## When to Use the Service Layer Pattern in Java
|
|
|
|
* 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.
|
|
|
|
## Real-World Applications of Service Layer Pattern in Java
|
|
|
|
* 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.
|
|
|
|
## Benefits and Trade-offs of Service Layer Pattern
|
|
|
|
Benefits:
|
|
|
|
Implementing a Service Layer in Java
|
|
|
|
* Promotes code reuse by encapsulating business logic in one place.
|
|
* Enhances testability by isolating business logic.
|
|
* Improves maintainability and flexibility of enterprise applications.
|
|
|
|
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
|
|
|
|
* [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.
|
|
|
|
## References and 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)
|
|
* [Service Layer (Martin Fowler)](http://martinfowler.com/eaaCatalog/serviceLayer.html)
|