|
9 | 9 | ---
|
10 | 10 |
|
11 | 11 | ## Intent
|
12 |
| -Service Layer is an abstraction over domain logic. Typically |
13 |
| -applications require multiple kinds of interfaces to the data they store and |
14 |
| -logic they implement: data loaders, user interfaces, integration gateways, and |
15 |
| -others. Despite their different purposes, these interfaces often need common |
16 |
| -interactions with the application to access and manipulate its data and invoke |
17 |
| -its business logic. The Service Layer fulfills this role. |
| 12 | + |
| 13 | +Service Layer is an abstraction over domain logic. It defines application's boundary with a layer of services that |
| 14 | +establishes a set of available operations and coordinates the application's response in each operation. |
| 15 | + |
| 16 | +## Explanation |
| 17 | + |
| 18 | +Typically applications require different kinds of interfaces to the data they store and the logic they implement. |
| 19 | +Despite their different purposes, these interfaces often need common interactions with the application to access and |
| 20 | +manipulate its data and invoke its business logic. Encoding the logic of the interactions separately in each module |
| 21 | +causes a lot of duplication. It's better to centralize building the business logic inside single Service Layer to avoid |
| 22 | +these pitfalls. |
| 23 | + |
| 24 | +Real world example |
| 25 | + |
| 26 | +> We are writing an application that tracks wizards, spellbooks and spells. Wizards may have spellbooks and spellbooks |
| 27 | +may have spells. |
| 28 | + |
| 29 | +In plain words |
| 30 | + |
| 31 | +> Service Layer is an abstraction over application's business logic. |
| 32 | +
|
| 33 | +Wikipedia says |
| 34 | + |
| 35 | +> Service layer is an architectural pattern, applied within the service-orientation design paradigm, which aims to |
| 36 | +organize the services, within a service inventory, into a set of logical layers. Services that are categorized into |
| 37 | +a particular layer share functionality. This helps to reduce the conceptual overhead related to managing the service |
| 38 | +inventory, as the services belonging to the same layer address a smaller set of activities. |
| 39 | + |
| 40 | +**Programmatic Example** |
| 41 | + |
| 42 | +The example application demonstrates interactions between a client `App` and a service `MagicService` that allows |
| 43 | +interaction between wizards, spellbooks and spells. The service is implemented with 3-layer architecture |
| 44 | +(entity, dao, service). |
| 45 | + |
| 46 | +For this explanation we are looking at one vertical slice of the system. Let's start from the entity layer and look at |
| 47 | +`Wizard` class. Other entities not shown here are `Spellbook` and `Spell`. |
| 48 | + |
| 49 | +```java |
| 50 | +@Entity |
| 51 | +@Table(name = "WIZARD") |
| 52 | +public class Wizard extends BaseEntity { |
| 53 | + |
| 54 | + @Id |
| 55 | + @GeneratedValue |
| 56 | + @Column(name = "WIZARD_ID") |
| 57 | + private Long id; |
| 58 | + |
| 59 | + private String name; |
| 60 | + |
| 61 | + @ManyToMany(cascade = CascadeType.ALL) |
| 62 | + private Set<Spellbook> spellbooks; |
| 63 | + |
| 64 | + public Wizard() { |
| 65 | + spellbooks = new HashSet<>(); |
| 66 | + } |
| 67 | + |
| 68 | + public Wizard(String name) { |
| 69 | + this(); |
| 70 | + this.name = name; |
| 71 | + } |
| 72 | + |
| 73 | + public Long getId() { |
| 74 | + return id; |
| 75 | + } |
| 76 | + |
| 77 | + public void setId(Long id) { |
| 78 | + this.id = id; |
| 79 | + } |
| 80 | + |
| 81 | + public String getName() { |
| 82 | + return name; |
| 83 | + } |
| 84 | + |
| 85 | + public void setName(String name) { |
| 86 | + this.name = name; |
| 87 | + } |
| 88 | + |
| 89 | + public Set<Spellbook> getSpellbooks() { |
| 90 | + return spellbooks; |
| 91 | + } |
| 92 | + |
| 93 | + public void setSpellbooks(Set<Spellbook> spellbooks) { |
| 94 | + this.spellbooks = spellbooks; |
| 95 | + } |
| 96 | + |
| 97 | + public void addSpellbook(Spellbook spellbook) { |
| 98 | + spellbook.getWizards().add(this); |
| 99 | + spellbooks.add(spellbook); |
| 100 | + } |
| 101 | + |
| 102 | + @Override |
| 103 | + public String toString() { |
| 104 | + return name; |
| 105 | + } |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +Above the entity layer we have DAOs. For `Wizard` the DAO layer looks as follows. |
| 110 | + |
| 111 | +```java |
| 112 | +public interface WizardDao extends Dao<Wizard> { |
| 113 | + |
| 114 | + Wizard findByName(String name); |
| 115 | +} |
| 116 | + |
| 117 | +public class WizardDaoImpl extends DaoBaseImpl<Wizard> implements WizardDao { |
| 118 | + |
| 119 | + @Override |
| 120 | + public Wizard findByName(String name) { |
| 121 | + Transaction tx = null; |
| 122 | + Wizard result; |
| 123 | + try (var session = getSessionFactory().openSession()) { |
| 124 | + tx = session.beginTransaction(); |
| 125 | + var criteria = session.createCriteria(persistentClass); |
| 126 | + criteria.add(Restrictions.eq("name", name)); |
| 127 | + result = (Wizard) criteria.uniqueResult(); |
| 128 | + tx.commit(); |
| 129 | + } catch (Exception e) { |
| 130 | + if (tx != null) { |
| 131 | + tx.rollback(); |
| 132 | + } |
| 133 | + throw e; |
| 134 | + } |
| 135 | + return result; |
| 136 | + } |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +Next we can look at the Service Layer, which in our case consists of a single `MagicService`. |
| 141 | + |
| 142 | +```java |
| 143 | +public interface MagicService { |
| 144 | + |
| 145 | + List<Wizard> findAllWizards(); |
| 146 | + |
| 147 | + List<Spellbook> findAllSpellbooks(); |
| 148 | + |
| 149 | + List<Spell> findAllSpells(); |
| 150 | + |
| 151 | + List<Wizard> findWizardsWithSpellbook(String name); |
| 152 | + |
| 153 | + List<Wizard> findWizardsWithSpell(String name); |
| 154 | +} |
| 155 | + |
| 156 | +public class MagicServiceImpl implements MagicService { |
| 157 | + |
| 158 | + private WizardDao wizardDao; |
| 159 | + private SpellbookDao spellbookDao; |
| 160 | + private SpellDao spellDao; |
| 161 | + |
| 162 | + public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { |
| 163 | + this.wizardDao = wizardDao; |
| 164 | + this.spellbookDao = spellbookDao; |
| 165 | + this.spellDao = spellDao; |
| 166 | + } |
| 167 | + |
| 168 | + @Override |
| 169 | + public List<Wizard> findAllWizards() { |
| 170 | + return wizardDao.findAll(); |
| 171 | + } |
| 172 | + |
| 173 | + @Override |
| 174 | + public List<Spellbook> findAllSpellbooks() { |
| 175 | + return spellbookDao.findAll(); |
| 176 | + } |
| 177 | + |
| 178 | + @Override |
| 179 | + public List<Spell> findAllSpells() { |
| 180 | + return spellDao.findAll(); |
| 181 | + } |
| 182 | + |
| 183 | + @Override |
| 184 | + public List<Wizard> findWizardsWithSpellbook(String name) { |
| 185 | + var spellbook = spellbookDao.findByName(name); |
| 186 | + return new ArrayList<>(spellbook.getWizards()); |
| 187 | + } |
| 188 | + |
| 189 | + @Override |
| 190 | + public List<Wizard> findWizardsWithSpell(String name) { |
| 191 | + var spell = spellDao.findByName(name); |
| 192 | + var spellbook = spell.getSpellbook(); |
| 193 | + return new ArrayList<>(spellbook.getWizards()); |
| 194 | + } |
| 195 | +} |
| 196 | +``` |
| 197 | + |
| 198 | +And finally we can show how the client `App` interacts with `MagicService` in the Service Layer. |
| 199 | + |
| 200 | +```java |
| 201 | + var service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); |
| 202 | + LOGGER.info("Enumerating all wizards"); |
| 203 | + service.findAllWizards().stream().map(Wizard::getName).forEach(LOGGER::info); |
| 204 | + LOGGER.info("Enumerating all spellbooks"); |
| 205 | + service.findAllSpellbooks().stream().map(Spellbook::getName).forEach(LOGGER::info); |
| 206 | + LOGGER.info("Enumerating all spells"); |
| 207 | + service.findAllSpells().stream().map(Spell::getName).forEach(LOGGER::info); |
| 208 | + LOGGER.info("Find wizards with spellbook 'Book of Idores'"); |
| 209 | + var wizardsWithSpellbook = service.findWizardsWithSpellbook("Book of Idores"); |
| 210 | + wizardsWithSpellbook.forEach(w -> LOGGER.info("{} has 'Book of Idores'", w.getName())); |
| 211 | + LOGGER.info("Find wizards with spell 'Fireball'"); |
| 212 | + var wizardsWithSpell = service.findWizardsWithSpell("Fireball"); |
| 213 | + wizardsWithSpell.forEach(w -> LOGGER.info("{} has 'Fireball'", w.getName())); |
| 214 | +``` |
| 215 | + |
18 | 216 |
|
19 | 217 | ## Class diagram
|
20 | 218 | 
|
|
0 commit comments