Java 中的服务层模式:使用健壮的服务层增强应用程序架构
大约 6 分钟
也称为
- 应用程序外观
服务层设计模式的意图
服务层模式对于专注于构建健壮应用程序架构的 Java 开发人员至关重要,这些架构将业务流程与用户界面问题分开。
该模式将业务逻辑封装在不同的层中,以促进关注点分离,并为表示层提供定义明确的 API。
使用真实世界示例详细说明服务层模式
现实世界示例
想象一个复杂的餐厅系统,其中订单通过集中的“服务层”进行管理,以确保高效的运营和前后端之间清晰的通信。每个部分专门负责餐食的一部分,但服务员不直接与厨房工作人员互动。相反,所有订单都通过主厨处理,主厨协调工作流程。主厨就像服务层,处理业务逻辑(订单协调)并为服务员(表示层)提供与厨房(数据访问层)交互的统一接口。
简单地说
一种将业务逻辑封装在独立层中的模式,以促进关注点分离并为表示层提供清晰的 API。
维基百科说
服务层是一种架构模式,应用于面向服务的设计范式中,旨在将服务目录中的服务组织成一组逻辑层。归类到特定层的服务共享功能。这有助于减少与管理服务目录相关的概念开销,因为属于同一层的服务处理的活动集更小。
Java 中服务层模式的编程示例
我们的 Java 实现使用服务层模式来简化数据访问对象 (DAO) 与业务逻辑之间的交互,确保关注点分离。
示例应用程序演示了客户端App
与服务MagicService
之间的交互,MagicService
允许巫师、魔法书和法术之间的交互。服务使用 3 层架构实现
(实体、DAO、服务)。
为了解释,我们将关注系统的垂直切片。从实体层开始,看看Wizard
类。其他未在此处显示的实体是Spellbook
和Spell
。
@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;
}
}
在实体层之上,我们有 DAO。对于Wizard
,DAO 层如下所示。
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;
}
}
接下来,我们可以看看服务层,在本例中包含一个MagicService
。
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());
}
}
最后,我们可以展示客户端App
如何在服务层中与MagicService
进行交互。
@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()));
}
}
程序输出
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'
使用真实世界示例详细说明服务层模式
何时在 Java 中使用服务层模式
- 当您需要将业务逻辑与表示逻辑分开时使用。
- 适用于具有复杂业务规则和工作流程的应用程序。
- 适用于需要为表示层提供清晰 API 的项目。
Java 中服务层模式的真实世界应用
- 使用企业 JavaBeans (EJB) 实现服务层的 Java EE 应用程序。
- 使用
@Service
注释表示服务层类的 Spring 框架应用程序。 - 需要将业务逻辑与控制器逻辑分开的 Web 应用程序。
服务层模式的优缺点
优点
在 Java 中实现服务层
- 通过将业务逻辑封装在一个地方来促进代码重用。
- 通过隔离业务逻辑来增强可测试性。
- 提高企业应用程序的可维护性和灵活性。
权衡
- 通过在应用程序中添加另一层,可能会引入额外的复杂性。
- 由于额外的抽象层,可能会导致性能开销。
相关的 Java 设计模式
- 外观:通过提供统一接口简化与复杂子系统的交互。
- DAO(数据访问对象):通常与服务层一起使用来处理数据持久性。
- MVC(模型-视图-控制器):服务层可用于封装模型组件中的业务逻辑。