diff --git a/service-layer/README.md b/service-layer/README.md index 3feaf8395..910eaeaea 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -9,12 +9,210 @@ tags: --- ## Intent -Service Layer is an abstraction over domain logic. Typically -applications require multiple kinds of interfaces to the data they store and -logic they implement: data loaders, user interfaces, integration gateways, and -others. Despite their different purposes, these interfaces often need common -interactions with the application to access and manipulate its data and invoke -its business logic. The Service Layer fulfills this role. + +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. + +## 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 + +> 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 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 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; + } +} +``` + +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); +} + +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; + } + return result; + } +} +``` + +Next we can look at the Service Layer, which in our case consists of a single `MagicService`. + +```java +public interface MagicService { + + List findAllWizards(); + + List findAllSpellbooks(); + + List findAllSpells(); + + List findWizardsWithSpellbook(String name); + + List 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 findAllWizards() { + return wizardDao.findAll(); + } + + @Override + public List findAllSpellbooks() { + return spellbookDao.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 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 ![alt text](./etc/service-layer.png "Service Layer")