Zuul的规则引擎允许基本上写任何JVM语言编写规则和过滤器,内置Java和Groovy。
注意 | 配置属性zuul.max.host.connections 已被两个新属性zuul.host.maxTotalConnections 和zuul.host.maxPerRouteConnections 替换,分别默认为200和20。 |
注意 | 所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)为SEMAPHORE。如果此隔离模式是首选,则zuul.ribbonIsolationStrategy 可以更改为THREAD。 |
如何加入Zuul
要在您的项目中包含Zuul,请使用组org.springframework.cloud
和artifact id spring-cloud-starter-zuul
的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
嵌入式Zuul反向代理
Spring Cloud已经创建了一个嵌入式Zuul代理,以简化UI应用程序想要代理对一个或多个后端服务的呼叫的非常常见的用例的开发。此功能对于用户界面对其所需的后端服务进行代理是有用的,避免了对所有后端独立管理CORS和验证问题的需求。
要启用它,使用@EnableZuulProxy
注释Spring Boot主类,并将本地调用转发到相应的服务。按照惯例,具有ID“用户”的服务将接收来自位于/users
(具有前缀stripped)的代理的请求。代理使用Ribbon来定位一个通过发现转发的实例,并且所有请求都以 hystrix命令执行,所以故障将显示在Hystrix指标中,一旦电路打开,代理将不会尝试联系服务。
注意 | Zuul启动器不包括发现客户端,因此对于基于服务ID的路由,您还需要在类路径中提供其中一个路由(例如Eureka)。 |
要跳过自动添加的服务,请将zuul.ignored-services
设置为服务标识模式列表。如果一个服务匹配一个被忽略的模式,而且包含在明确配置的路由映射中,那么它将被无符号。例:
application.yml
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
在此示例中,除 “用户” 之外,所有服务都被忽略。
要扩充或更改代理路由,可以添加如下所示的外部配置:
application.yml
zuul:
routes:
users: /myusers/**
这意味着对“/ myusers”的http呼叫转发到“用户”服务(例如“/ myusers / 101”转发到“/ 101”)。
要获得对路由的更细粒度的控制,您可以独立地指定路径和serviceId:
application.yml
zuul:
routes:
users:
path: /myusers/**
serviceId: users_service
这意味着对“/ myusers”的http呼叫转发到“users_service”服务。路由必须有一个“路径”,可以指定为蚂蚁样式模式,所以“/ myusers / *”只匹配一个级别,但“/ myusers / **”分层匹配。
后端的位置可以被指定为“serviceId”(用于发现的服务)或“url”(对于物理位置),例如
application.yml
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
这些简单的URL路由不会被执行为HystrixCommand
,也不能使用Ribbon对多个URL进行负载平衡。为此,请指定service-route并为serviceId配置Ribbon客户端(目前需要在Ribbon中禁用Eureka支持:详见上文),例如
application.yml
zuul:
routes:
users:
path: /myusers/**
serviceId: users
ribbon:
eureka:
enabled: false
users:
ribbon:
listOfServers: example.com,google.com
您可以使用regexmapper在serviceId和路由之间提供约定。它使用名为group的正则表达式从serviceId中提取变量并将它们注入到路由模式中。
ApplicationConfiguration.java
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
这意味着serviceId“myusers-v1”将被映射到路由“/ v1 / myusers / **”。任何正则表达式都被接受,但所有命名组都必须存在于servicePattern和routePattern中。如果servicePattern与serviceId不匹配,则使用默认行为。在上面的示例中,serviceId“myusers”将被映射到路由“/ myusers / **”(检测不到版本)此功能默认禁用,仅适用于已发现的服务。
要为所有映射添加前缀,请将zuul.prefix
设置为一个值,例如/api
。默认情况下,请求被转发之前,代理前缀被删除(使用zuul.stripPrefix=false
关闭此行为)。您还可以关闭从各路线剥离服务特定的前缀,例如
application.yml
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
注意 | zuul.stripPrefix 仅适用于zuul.prefix 中设置的前缀。它对给定路由path 中定义的前缀有影响。 |
在本示例中,对“/ myusers / 101”的请求将转发到“/ myusers / 101”上的“users”服务。
zuul.routes
条目实际上绑定到类型为ZuulProperties
的对象。如果您查看该对象的属性,您将看到它还具有“可重试”标志。将该标志设置为“true”使Ribbon客户端自动重试失败的请求(如果需要,可以使用Ribbon客户端配置修改重试操作的参数)。
默认情况下,将X-Forwarded-Host
标头添加到转发的请求中。关闭set zuul.addProxyHeaders = false
。默认情况下,前缀路径被删除,对后端的请求会拾取一个标题“X-Forwarded-Prefix”(上述示例中的“/ myusers”)。
如果您设置默认路由(“/”),则@EnableZuulProxy
的应用程序可以作为独立服务器,例如zuul.route.home: /
将路由所有流量(即“/ **”)到“home”服务。
如果需要更细粒度的忽略,可以指定要忽略的特定模式。在路由位置处理开始时评估这些模式,这意味着前缀应包含在模式中以保证匹配。忽略的模式跨越所有服务,并取代任何其他路由规范。
application.yml
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
这意味着诸如“/ myusers / 101”的所有呼叫将被转发到“用户”服务上的“/ 101”。但是包含“/ admin /”的呼叫将无法解决。
警告 | 如果您需要您的路由保留订单,则需要使用YAML文件,因为使用属性文件将会丢失订购。例如: |
application.yml
zuul:
routes:
users:
path: /myusers/**
legacy:
path: /**
如果要使用属性文件,则legacy
路径可能会在users
路径前面展开,从而使users
路径不可达。
Zuul Http客户端
zuul使用的默认HTTP客户端现在由Apache HTTP Client支持,而不是不推荐使用的Ribbon RestClient
。要分别使用RestClient
或使用okhttp3.OkHttpClient
集合ribbon.restclient.enabled=true
或ribbon.okhttp.enabled=true
。
Cookie和敏感标题
在同一个系统中的服务之间共享标题是可行的,但是您可能不希望敏感标头泄漏到外部服务器的下游。您可以在路由配置中指定被忽略头文件列表。Cookies起着特殊的作用,因为它们在浏览器中具有明确的语义,并且它们总是被视为敏感的。如果代理的消费者是浏览器,则下游服务的cookie也会导致用户出现问题,因为它们都被混淆(所有下游服务看起来都是来自同一个地方)。
如果您对服务的设计非常谨慎,例如,如果只有一个下游服务设置了Cookie,那么您可能可以让他们从后台一直到调用者。另外,如果您的代理设置cookie和所有后台服务都是同一系统的一部分,那么简单地共享它们就可以自然(例如使用Spring Session将它们链接到一些共享状态)。除此之外,由下游服务设置的任何Cookie可能对呼叫者来说都不是很有用,因此建议您将(至少)“Set-Cookie”和“Cookie”设置为不属于您的域名。即使是属于您域名的路线,请尝试仔细考虑允许Cookie在代理之间流动的含义。
灵敏头可以配置为每个路由的逗号分隔列表,例如
application.yml
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
注意 | 这是sensitiveHeaders 的默认值,因此您不需要设置它,除非您希望它不同。注意这是Spring Cloud Netflix 1.1中的新功能(1.0中,用户无法控制标题,所有Cookie都在两个方向上流动)。 |
sensitiveHeaders
是一个黑名单,默认值不为空,所以要使Zuul发送所有标题(“被忽略”除外),您必须将其显式设置为空列表。如果您要将Cookie或授权标头传递到后端,这是必要的。例:
application.yml
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
也可以通过设置zuul.sensitiveHeaders
来全局设置敏感标题。如果在路由上设置sensitiveHeaders
,则将覆盖全局sensitiveHeaders
设置。
被忽略的标题
除了每个路由的敏感标头,您还可以为与下游服务交互期间应该丢弃的值(请求和响应)设置全局值为zuul.ignoredHeaders
。默认情况下,如果Spring安全性不在类路径上,则它们是空的,否则它们被初始化为由Spring Security指定的一组众所周知的“安全性”头(例如涉及缓存)。在这种情况下的假设是下游服务可能也添加这些头,我们希望代理的值。为了不丢弃这些众所周知的安全标头,只要Spring安全性在类路径上,您可以将zuul.ignoreSecurityHeaders
设置为false
。如果您禁用Spring安全性中的HTTP安全性响应头,并希望由下游服务提供的值,这可能很有用
路线端点
如果您在Spring Boot执行器中使用@EnableZuulProxy
,您将启用(默认情况下)另一个端点,通过HTTP可用/routes
。到此端点的GET将返回映射路由的列表。POST将强制刷新现有路由(例如,如果服务目录中有更改)。您可以通过将endpoints.routes.enabled
设置为false
来禁用此端点。
注意 | 路由应自动响应服务目录中的更改,但POST到/路由是强制更改立即发生的一种方式。 |
扼杀模式和本地前进
迁移现有应用程序或API时的常见模式是“扼杀”旧端点,用不同的实现慢慢替换它们。Zuul代理是一个有用的工具,因为您可以使用它来处理来自旧端点的客户端的所有流量,但将一些请求重定向到新端点。
示例配置:
application.yml
zuul:
routes:
first:
path: /first/**
url: http://first.example.com
second:
path: /second/**
url: forward:/second
third:
path: /third/**
url: forward:/3rd
legacy:
path: /**
url: http://legacy.example.com
在这个例子中,我们扼杀了“遗留”应用程序,该应用程序映射到所有与其他模式不匹配的请求。/first/**
中的路径已被提取到具有外部URL的新服务中。并且/second/**
中的路径被转发,以便它们可以在本地处理,例如具有正常的Spring @RequestMapping
。/third/**
中的路径也被转发,但具有不同的前缀(即/third/foo
转发到/3rd/foo
)。
注意 | 被忽略的模式并不完全被忽略,它们只是不被代理处理(因此它们也被有效地转发到本地)。 |
通过Zuul上传文件
如果您@EnableZuulProxy
您可以使用代理路径上传文件,只要文件很小,它就应该工作。对于大文件,有一个替代路径绕过“/ zuul / *”中的Spring DispatcherServlet
(以避免多部分处理)。也就是说,如果zuul.routes.customers=/customers/**
则可以将大文件发送到“/ zuul / customers / *”。servlet路径通过zuul.servletPath
进行外部化。如果代理路由引导您通过Ribbon负载均衡器,例如,超大文件也将需要提升超时设置
application.yml
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
请注意,要使用大型文件进行流式传输,您需要在请求中使用分块编码(某些浏览器默认情况下不会执行)。例如在命令行:
$ curl -v -H "Transfer-Encoding: chunked" \
-F "file=@mylarge.iso" localhost:9999/zuul/simple/file
查询字符串编码
处理传入的请求时,查询参数被解码,因此可以在Zuul过滤器中进行修改。然后在路由过滤器中构建后端请求时重新编码它们。如果使用Javascript的encodeURIComponent()
方法编码,结果可能与原始输入不同。虽然这在大多数情况下不会出现任何问题,但一些Web服务器可以用复杂查询字符串的编码来挑选。
要强制查询字符串的原始编码,可以将特殊标志传递给ZuulProperties
,以便查询字符串与HttpServletRequest::getQueryString
方法相同:
application.yml
zuul:
forceOriginalQueryStringEncoding: true
注意:此特殊标志仅适用于SimpleHostRoutingFilter
,您可以使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)
轻松覆盖查询参数,因为查询字符串现在直接在原始的HttpServletRequest
上获取。
普通嵌入Zuul
如果您使用@EnableZuulServer
(而不是@EnableZuulProxy
),您也可以运行不带代理的Zuul服务器,或者有选择地切换代理平台的部分。您添加到ZuulFilter
类型的应用程序的任何bean都将自动安装,与@EnableZuulProxy
一样,但不会自动添加任何代理过滤器。
在这种情况下,仍然通过配置“zuul.routes。*”来指定进入Zuul服务器的路由,但没有服务发现和代理,所以“serviceId”和“url”设置将被忽略。例如:
application.yml
zuul:
routes:
api: /api/**
将“/ api / **”中的所有路径映射到Zuul过滤器链。
禁用Zuul过滤器
Spring Cloud的Zuul在代理和服务器模式下默认启用了多个ZuulFilter
bean。有关启用的可能过滤器,请参阅zuul过滤器包。如果要禁用它,只需设置zuul.<SimpleClassName>.<filterType>.disable=true
。按照惯例,filters
之后的包是Zuul过滤器类型。例如,禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
设置zuul.SendResponseFilter.post.disable=true
。
为路线提供Hystrix回退
当Zuul中给定路由的电路跳闸时,您可以通过创建类型为ZuulFallbackProvider
的bean来提供回退响应。在这个bean中,您需要指定回退的路由ID,并提供返回的ClientHttpResponse
作为后备。这是一个非常简单的ZuulFallbackProvider
实现。
class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "customers";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
这里是路由配置的样子。
zuul:
routes:
customers: /customers/**
如果您希望为所有路由提供默认的回退,您可以创建一个类型为ZuulFallbackProvider
的bean,并且getRoute
方法返回*
或null
。
class MyFallbackProvider implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("fallback".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
Zuul开发人员指南
有关Zuul如何工作的一般概述,请参阅Zuul维基。
Zuul Servlet
Zuul被实现为Servlet。对于一般情况,Zuul嵌入到Spring调度机制中。这允许Spring MVC控制路由。在这种情况下,Zuul被配置为缓冲请求。如果需要通过Zuul不缓冲请求(例如大文件上传),Servlet也将安装在Spring调度程序之外。默认情况下,它位于/zuul
。可以使用zuul.servlet-path
属性更改此路径。
Zuul RequestContext
要在过滤器之间传递信息,Zuul使用a RequestContext。其数据按照每个请求的ThreadLocal
进行。关于路由请求,错误以及实际HttpServletRequest
和HttpServletResponse
的路由信息。RequestContext
扩展ConcurrentHashMap
,所以任何东西都可以存储在上下文中。FilterConstants包含由Spring Cloud Netflix安装的过滤器使用的密钥(稍后再安装)。
@EnableZuulProxy
与@EnableZuulServer
Spring Cloud Netflix根据使用何种注释来启用Zuul安装多个过滤器。@EnableZuulProxy
是@EnableZuulServer
的超集。换句话说,@EnableZuulProxy
包含@EnableZuulServer
安装的所有过滤器。“代理”中的其他过滤器启用路由功能。如果你想要一个“空白”Zuul,你应该使用@EnableZuulServer
。
@EnableZuulServer
过滤器
创建从Spring Boot配置文件加载路由定义的SimpleRouteLocator
。
安装了以下过滤器(正常Spring豆类):
前置过滤器
-
ServletDetectionFilter
:检测请求是否通过Spring调度程序。使用键FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
设置布尔值。 -
FormBodyWrapperFilter
:解析表单数据,并对下游请求进行重新编码。 -
DebugFilter
:如果设置debug
请求参数,则此过滤器将RequestContext.setDebugRouting()
和RequestContext.setDebugRequest()
设置为true。
路由过滤器
-
SendForwardFilter
:此过滤器使用ServletRequestDispatcher
转发请求。转发位置存储在RequestContext
属性FilterConstants.FORWARD_TO_KEY
中。这对于转发到当前应用程序中的端点很有用。
过滤器:
-
SendResponseFilter
:将代理请求的响应写入当前响应。
错误过滤器:
-
SendErrorFilter
:如果RequestContext.getThrowable()
不为null,则转发到/错误(默认情况下)。可以通过设置error.path
属性来更改默认转发路径(/error
)。
@EnableZuulProxy
过滤器
创建从DiscoveryClient
(如Eureka)以及属性加载路由定义的DiscoveryClientRouteLocator
。每个serviceId
从DiscoveryClient
创建路由。随着新服务的添加,路由将被刷新。
除了上述过滤器之外,还安装了以下过滤器(正常Spring豆类):
前置过滤器
-
PreDecorationFilter
:此过滤器根据提供的RouteLocator
确定在哪里和如何路由。它还为下游请求设置各种与代理相关的头。
路由过滤器
-
RibbonRoutingFilter
:此过滤器使用Ribbon,Hystrix和可插拔HTTP客户端发送请求。服务ID位于RequestContext
属性FilterConstants.SERVICE_ID_KEY
中。此过滤器可以使用不同的HTTP客户端。他们是:-
Apache
HttpClient
。这是默认的客户端。 -
Squareup
OkHttpClient
v3。通过在类路径上设置com.squareup.okhttp3:okhttp
库并设置ribbon.okhttp.enabled=true
来启用此功能。 -
Netflix Ribbon HTTP客户端。这可以通过设置
ribbon.restclient.enabled=true
来启用。这个客户端有限制,比如它不支持PATCH方法,还有内置的重试。
-
-
SimpleHostRoutingFilter
:此过滤器通过Apache HttpClient发送请求到预定的URL。URL位于RequestContext.getRouteHost()
。
自定义Zuul过滤示例
以下大部分以下“如何撰写”示例都包含示例Zuul过滤器项目。还有一些操作该存储库中的请求或响应正文的例子。
如何编写预过滤器
前置过滤器用于设置RequestContext
中的数据,用于下游的过滤器。主要用例是设置路由过滤器所需的信息。
public class QueryParamPreFilter extends ZuulFilter {
@Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getParameter("foo") != null) {
// put the serviceId in `RequestContext`
ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
}
return null;
}
}
上面的过滤器从foo
请求参数填充SERVICE_ID_KEY
。实际上,做这种直接映射并不是一个好主意,而是从foo
的值来查看服务ID。
现在填写SERVICE_ID_KEY
,PreDecorationFilter
将不会运行,RibbonRoutingFilter
将会。如果您想要路由到完整的网址,请改用ctx.setRouteHost(url)
。
要修改路由过滤器将转发的路径,请设置REQUEST_URI_KEY
。
如何编写路由过滤器
路由过滤器在预过滤器之后运行,并用于向其他服务发出请求。这里的大部分工作是将请求和响应数据转换到客户端所需的模型。
public class OkHttpRoutingFilter extends ZuulFilter {
@Autowired
private ProxyRequestHelper helper;
@Override
public String filterType() {
return ROUTE_TYPE;
}
@Override
public int filterOrder() {
return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
}
@Override
public Object run() {
OkHttpClient httpClient = new OkHttpClient.Builder()
// customize
.build();
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String method = request.getMethod();
String uri = this.helper.buildZuulRequestURI(request);
Headers.Builder headers = new Headers.Builder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
headers.add(name, value);
}
}
InputStream inputStream = request.getInputStream();
RequestBody requestBody = null;
if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
MediaType mediaType = null;
if (headers.get("Content-Type") != null) {
mediaType = MediaType.parse(headers.get("Content-Type"));
}
requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
}
Request.Builder builder = new Request.Builder()
.headers(headers.build())
.url(uri)
.method(method, requestBody);
Response response = httpClient.newCall(builder.build()).execute();
LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();
for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
responseHeaders.put(entry.getKey(), entry.getValue());
}
this.helper.setResponse(response.code(), response.body().byteStream(),
responseHeaders);
context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
return null;
}
}
上述过滤器将Servlet请求信息转换为OkHttp3请求信息,执行HTTP请求,然后将OkHttp3响应信息转换为Servlet响应。警告:此过滤器可能有错误,但功能不正确。
如何编写过滤器
后置过滤器通常操纵响应。在下面的过滤器中,我们添加一个随机UUID
作为X-Foo
头。其他操作,如转换响应体,要复杂得多,计算密集。
public class AddResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return SEND_RESPONSE_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
servletResponse.addHeader("X-Foo", UUID.randomUUID().toString());
return null;
}
}
Zuul错误如何工作
如果在Zuul过滤器生命周期的任何部分抛出异常,则会执行错误过滤器。SendErrorFilter
只有RequestContext.getThrowable()
不是null
才会运行。然后在请求中设置特定的javax.servlet.error.*
属性,并将请求转发到Spring Boot错误页面。
Zuul渴望应用程序上下文加载
Zuul内部使用Ribbon调用远程URL,并且Ribbon客户端默认在第一次调用时由Spring Cloud加载。可以使用以下配置更改Zuul的此行为,并将导致在应用程序启动时,子Ribbon相关的应用程序上下文正在加载。
application.yml
zuul:
ribbon:
eager-load:
enabled: true