#590 explanation for Service Layer

This commit is contained in:
Ilkka Seppälä 2020-07-26 19:25:25 +03:00
parent 3e1a83e29d
commit f37d697a60

View File

@ -9,12 +9,210 @@ tags:
--- ---
## Intent ## Intent
Service Layer is an abstraction over domain logic. Typically
applications require multiple kinds of interfaces to the data they store and Service Layer is an abstraction over domain logic. It defines application's boundary with a layer of services that
logic they implement: data loaders, user interfaces, integration gateways, and establishes a set of available operations and coordinates the application's response in each operation.
others. Despite their different purposes, these interfaces often need common
interactions with the application to access and manipulate its data and invoke ## Explanation
its business logic. The Service Layer fulfills this role.
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
> We are writing an application that tracks wizards, spellbooks and spells. Wizards may have spellbooks and spellbooks
may have spells.
In plain words
> Service Layer is an abstraction over application's business logic.
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.
**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
(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")
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 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) {
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);
}
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 WizardDao wizardDao;
private SpellbookDao spellbookDao;
private 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
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()));
```
## Class diagram ## Class diagram
![alt text](./etc/service-layer.png "Service Layer") ![alt text](./etc/service-layer.png "Service Layer")