- 关于
- 学习
- 社区
- 开始编码
博客用数据库中的模板使用Qute
用数据库中的模板使用Qute
作者:Gwenneg Lepage
简介
我是红帽团队的一员,他们创建了一个多租户通知服务,从许多红帽混合云控制台应用程序(租户)发送通知。我们的服务可以用来发送几种类型的通知,包括电子邮件。每个租户可以根据自己的需要创建尽可能多的电子邮件模板,并将它们与将触发通知的事件联系起来。
我们通过神奇的Qute模板引擎和以文件形式存储在src/main/resources/templates 文件夹中的模板来实现。它允许我们的租户在对Qute了解不多的情况下设计适合他们需要的模板。然而,我们很快意识到,对租户来说,编辑模板是一个缓慢而沉重的过程。事实上,他们必须在我们的存储库中创建一个GitHub拉动请求,等待审查,然后再次等待部署,然后才能测试模板。我们需要使这个过程对租户来说更容易,最好是自我服务。
然后我们决定使用Qute的TemplateLocator ,将模板从文件存储转移到数据库中。它帮助我们为租户提供了一个更容易、无摩擦和自助式的编辑模板的方式。
以下是我们如何做的。
显而易见的部分:将模板持久化到数据库中
在使用Qute数据库中的模板之前,模板显然需要被持久化。如何执行并不重要。任何类型的Hibernate(无论是否反应式,无论是否有Panache)都可以工作。这篇文章将展示基于Hibernate与Panache的例子。
接下来的章节将使用这个JPA实体。
package org.acme;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class DbTemplate extends PanacheEntityBase {
@Id
public String name; (1)
public String content;
}
| 1 | 模板名称将是DB的主键。 |
有趣的部分:连接Qute到数据库
现在模板可以被持久化了,我们需要一种方法来从Qute使用它们。幸运的是,Qute带有一个非常有趣的接口,叫做TemplateLocator ,可以用来从任何地方加载模板,包括从数据库。
下面是它如何与我们前面定义的DbTemplate 实体一起使用。
package org.acme;
import io.quarkus.logging.Log;
import io.quarkus.qute.EngineBuilder;
import io.quarkus.qute.TemplateLocator;
import io.quarkus.qute.Variant;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import java.io.Reader;
import java.io.StringReader;
import java.util.Optional;
@ApplicationScoped
public class DbTemplateLocator implements TemplateLocator {
@Override
public Optional<TemplateLocation> locate(String name) {
DbTemplate template = DbTemplate.findById(name);
if (template == null) {
Log.tracef("Template with [name=%s] not found in the database", name);
return Optional.empty();
} else {
Log.tracef("Template with [name=%s] found in the database", name);
return Optional.of(buildTemplateLocation(template.getContent()));
}
}
@Override
public int getPriority() { (1)
return DEFAULT_PRIORITY - 1;
}
void configureEngine(@Observes EngineBuilder builder) { (2)
builder.addLocator(this);
}
private TemplateLocation buildTemplateLocation(String templateContent) {
return new TemplateLocation() {
@Override
public Reader read() {
return new StringReader(templateContent);
}
@Override
public Optional<Variant> getVariant() {
return Optional.empty();
}
};
}
}
| 1 | 如果你的Quarkus应用程序包含从文件系统和数据库加载的模板,你将需要覆盖模板定位器的默认优先级。否则,Quarkus将尝试使用DbTemplateLocator 从文件系统加载模板,这可能导致异常或不可预测的行为。 |
| 2 | 在Quarkus 2.10之前,将DbTemplateLocator 与Quarkus提供的Qute引擎实例集成只能通过这样的CDI观察器完成。 |
Quarkus 2.10最近引入了一个新的 |
现在模板定位器已经注册了,我们准备用Qute从数据库中编译和渲染模板。正如你在下面的例子中看到的,DB模板的使用与文件模板完全一样。
package org.acme;
import io.quarkus.qute.Engine;
import io.quarkus.qute.Template;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
@ApplicationScoped
public class EmailSender {
@Inject
Engine engine;
public void sendEmail(String templateName) {
Template template = engine.getTemplate(templateName);
if (template != null) {
String rendered = template.render();
// Send an email using the template.
}
}
}
小心Qute的内部缓存
每当Qute加载一个模板,它就会被存储到一个内部的ConcurrentHashMap ,并永远留在内存中,除非Qute另有指示。这意味着,在数据库中更新或删除DB模板后,你需要从Qute的内部缓存中删除它。
有几种方法可以实现这一点。
package org.acme;
import io.quarkus.qute.Engine;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
@ApplicationScoped
public class DbEngineCacheManager {
@Inject
Engine engine;
public void removeTemplates(String name) {
engine.removeTemplates(templateName -> templateName.equals(name)); (1)
}
public void clearAll() {
engine.clearTemplates(); (2)
}
}
| 1 | 这将删除映射ID与给定谓词相匹配的模板。 |
| 2 | 这将从缓存中删除所有模板。 |
如果你的应用程序运行在有多个副本的Kubernetes集群上,清除内部缓存会变得很棘手。你确实需要一种方法来向所有的pods广播(可能使用Kafka主题或DB表),以从缓存中删除已经更新或删除的模板的指令。有一个更便宜的方法(但非常不完美),可以使用一个计划的工作来保持所有pods缓存的同步。
package org.acme;
import io.quarkus.qute.Engine;
import io.quarkus.scheduler.Scheduled;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
@ApplicationScoped
public class DbEngineCacheScheduledCleaner {
@Inject
Engine engine;
@Scheduled(every = "5m", delayed = "5m") (1)
public void clearTemplates() {
engine.clearTemplates();
}
}
| 1 | 所有的模板将每5分钟从内部缓存中被清除。 |
防止删除一个被包含的模板
一个Qute模板可以被包含在另一个模板中。如果内部模板被删除,那么外部模板的编译就会失败,这显然是在从数据库加载模板时需要防止的。
这里有一个方法,在删除一个模板之前,寻找是否将其包含到另一个模板中。
package org.acme;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.transaction.Transactional;
@ApplicationScoped
public class TemplateRepository {
@Inject
EntityManager entityManager;
@Transactional
public void deleteTemplate(String name) {
long count = entityManager.createQuery("SELECT COUNT(*) FROM DbTemplate WHERE name != :name AND content LIKE :include", Long.class)
.setParameter("name", name)
.setParameter("include", "%{#include " + name + "%")
.getSingleResult();
if (count > 0) {
throw new IllegalStateException("Included templates can't be deleted, remove the inclusion or delete the outer template first");
} else {
entityManager.createQuery("DELETE FROM DbTemplate WHERE name = :name")
.setParameter("name", name)
.executeUpdate();
}
}
}
数据库模板验证
数据库模板有一个明显的缺点。Quarkus不再能够执行类型安全的验证。
语法验证也会从构建时间延迟到运行时间,但这是意料之中的,因为模板可以在运行时间创建或编辑。
特别感谢
感谢Josejulio Martinez Magana和Martin Kouba在我们的通知服务中实现DB模板的过程中对我的帮助!
Quarkus是开放的。这个项目的所有依赖性都可以在Apache软件许可2.0或兼容许可下使用。
这个网站是用Jekyll建立的,托管在GitHub Pages上,是完全开源的。如果你想让它变得更好,请分叉该网站并向我们展示你的成果。
导航
关注我们
获取帮助
语言
Quarkus是由社区项目组成的