其中一个重大变化是 Mendix 7 引入了无状态运行时,其中状态从运行时转移到客户端。在 Mendix 7 中,客户端现在会跟踪状态并将其与浏览器和移动应用的请求一起发送到运行时。此功能允许运行时水平扩展,当应用程序需要更多服务器资源时,可提供极大的灵活性。
在本博客系列的第一部分中,我们将通过一个应用程序向您展示每一步从状态角度发生的情况,以便您更好地了解客户端状态的工作方式。
免责声明:本博客文章适用于 Mendix 7.23.1. 状态行为可能与之前的版本有所不同,并且可能会在后续版本中更改,恕不另行通知。
什么是状态以及它实际上包含什么?
作为 Mendix 应用程序,您使用的部分数据可能尚未存储在数据库中。确切地说,状态包括:
- 所有新创建且尚未提交的持久对象 (PE)
- 所有不可持久对象 (NPE)
- 对对象所做的所有属性和关联更改(关联和属性在客户端中被视为相同,因此本文其余部分将不再提及关联)
这些对象和更改构成了状态。由于它们尚未存储在应用的数据库中,因此您需要将它们存储在状态中,直到它们被存储在数据库中、被丢弃或不再在应用中需要为止。
可以通过不同方式来修改状态:
- 可以通过运行时中的微流、客户端中的纳米流或创建对象客户端操作来创建新对象
- 属性更改可以由微流、纳米流、自定义小部件或最终用户进行
从状态的角度来看,对象或更改的来源并不重要。所有对象和更改最终都会进入客户端状态。
状态可能包含比之前列出的更多的对象。有时状态甚至包含已提交的对象。这通常是为了提高应用程序的性能。
什么是变化以及它如何存储在状态中?
更改是指未提交的值 Mendix 对象的属性。例如,当用户使用文本框更改属性值时, Mendix 将新值存储为已编辑对象状态的更改。 Mendix 对象本身尚未改变。
保持变更与 Mendix 对象使回滚成为可能。每当你回滚 Mendix 微流、纳米流中的对象,或具有取消更改客户端操作的对象, Mendix 只是丢弃对状态中的对象所做的所有更改。
这意味着每当用户点击保存按钮时, Mendix 收集并发送对已编辑对象所做的所有更改,以便将这些更改保存到运行时的数据库中。对于需要更改对象的其他运行时请求(例如微流调用),也是如此。
国家范围
与 Mendix 7、状态存储在浏览器的内存中 Mendix 客户端。这意味着对于 Web 应用,状态仅限于当前浏览器选项卡。如果您在单独的选项卡中打开应用,则无法访问上一个选项卡的状态。这也意味着刷新当前浏览器选项卡将导致其状态丢失。
最后一种情况有一个例外。 Mendix 客户端存储你的状态到 会话存储 暂时关闭浏览器以使用我们的快速部署功能。这样您就可以从更改模型时所在的页面或状态继续在应用程序中工作,然后运行它。此功能仅在开发期间可用。
上述两种行为对于 Mendix 7,与内置应用程序不兼容 Mendix 6 年及之前。 在 Mendix 6、仍然可以从不同的选项卡访问状态,并且刷新后仍然有效。
国家与安全
在客户端存储状态会产生一些安全影响:
当前用户的只读属性 Mendix 使用值旁边的哈希值保护当前用户的只读属性值,因此任何非法更改这些值的尝试都会被捕获和拒绝。
更改当前用户无法访问的属性 登录用户无法访问的属性的更改无法存储在状态中。将它们发送给客户端可能会泄露机密数据。因此,此类更改将被丢弃。
状态如何传达给运行时?
此 Mendix 客户端通过特殊端点与运行时进行通信 /xas例如,在为数据网格加载数据或调用微流时。对此 API 的每种调用类型都称为操作,状态的必要部分会随其一起发送。您可以检查 xas 请求 网络 浏览器开发者工具选项卡并过滤对 xas 路径。我们将访问 xas 稍后会在帖子中提供 API。
免责声明: xas 是 Mendix 客户端和运行时,未来版本可能会发生变化,恕不另行通知。此处提供的详细信息旨在提高对如何 Mendix 应用程序与运行时进行通信,以便您可以建模更好的应用程序并更有效地解决应用程序问题。
每当一个 xas 触发操作(例如微流调用), Mendix 客户端也发送状态。 Mendix 客户端不会将整个状态发送到运行时,因为这会产生严重的性能问题。 Mendix 通过分析应用程序部署期间的每个微流来决定应该发送哪些部分的状态。
我可以检查客户端状态吗?
按 按Ctrl+其他+G 组合键会将客户端状态摘要转储到浏览器控制台,以便您可以检查存储的内容。我们将使用此快捷方式检查我们的示例应用程序。
艺术品注册应用程序
为了演示客户端状态,我构建了一个简单的艺术品注册应用程序,可以在其中存储有关艺术家的信息。
您可以下载该项目 开始 并在我们进行的过程中检查状态。
以下是我们的简单领域模型。 艺人 实体与 全名 属性。
让我们运行我们的应用程序。主页故意是空的,这样 Mendix 客户端从空状态开始。

我们可以通过打开来验证这一点 安慰 在开发者工具中,按下 Ctrl + Alt + G 捷径。

这里您可以看到状态表示为 JSON 对象,但它目前是空的。
点击 艺术家 菜单项。这里是应用程序中定义的所有艺术家的列表视图。目前没有。

点击创建新艺术家 创建新艺术家 按钮并检查发生了什么 网络 开发人员工具选项卡。选择第一个请求 xas/ 路径并向下滚动 头 详细视图中的选项卡:

提取请求负载并检查:
Switch to "Text" tab and paste code here
上面你看到的是典型的 xas 请求。让我们来看看每个字段:
action:我们提到所有运行时操作都会经过xasAPI,因此每个操作必须是可区分的。这里action字段用于此目的。当前操作是instantiate,要求运行时创建一个 Mendix 目的。params:每个xas动作可能有自己的参数集,因此params包含特定于操作的参数。对于instantiate调用时,运行时需要知道要创建什么类型的对象,因此它被传递到objecttype领域。changes:这是与客户端状态相关的字段之一。每当xas提出请求后, Mendix 客户端通过请求发送相关状态,并且changes是该状态的一部分。由于创建新对象不需要任何状态,因此这里为空。objects:这是与客户端状态相关的第二个字段。此处 Mendix 客户端提供运行时在请求期间可能需要的状态的对象。由于创建新对象不需要任何对象状态,因此它也是空的。
选择 预览 选项卡内 网络 请求详细查看:

在检查细节时,我将跳过开发人员工具的屏幕截图,仅显示提取的版本:
{
"actionResult": "5629499534213124",
"commits": [],
"changes": {},
"resets": {},
"deletes": [],
"newpersistable": [
"5629499534213124"
],
"objects": [
{
"objectType": "MyFirstModule.Artist",
"guid": "5629499534213124",
"hash": "Nw09VTjCQKib7DbE5Agxgm3Bg6fEGL4mPRyNrgiUKGs=",
"attributes": {
"FullName": {
"value": null
}
}
}
]
}
上面你看到的是典型的 xas 回复。 这 actionResult 字段与动作本身直接相关,但其他字段与客户端状态相关。
每次调用 xas,运行时响应会保留有关请求期间发生的情况的信息,例如创建或删除了哪些对象。运行时会在每次响应中向客户端传达这些信息,以便客户端将这些更改应用于其状态。
以下是与客户端状态相关的响应字段的概述:
提交
此字段包含请求期间提交的 guid 列表。由于创建本身不会提交对象,因此它是空的。填充后它看起来如下所示:
"commits": ["guid_1", "guid_2", "...guid_n"]
此 Mendix 客户端使用此信息来了解新对象何时提交。这样它就可以相应地更新客户端状态。
更改
此字段包含对 Mendix 请求期间所做的对象。这意味着这些更改尚未提交,因此应将它们存储在状态中。
此字段未填充 instantiate 呼叫,因为新 Artist 反对。但如果 Artist 对象有一个 After Create 事件微流,它将艺术家的名字更改为默认值,该字段看起来会像这样:
"changes": {
"5629499534213124": {
"FullName": {
"value": "Value set in the event microflow"
}
}
}
此 Mendix 客户端在此处进行更改并将其应用于客户端状态。
重设
此字段包含每个回滚属性更改的所有摘要 Mendix 目的。 Mendix 客户端使用该信息从其状态中删除现有更改。以下是重置的示例:
"resets": {
"guid_of_the_object": ["attribute_1", "attribute_2"]
}
删除
此字段包含以下 guid 列表: Mendix 在请求期间被删除的对象。 Mendix 客户端使用该删除信息从状态中删除相应的对象及其更改。上面的示例请求中此字段为空,但以下是删除的示例:
"deletes": ["guid_1", "guid_2", "...guid_n"]
新持久性
此字段包含在请求期间创建的 guid 列表,因此 Mendix 客户端知道它们尚未提交,并且需要根据需要存储它们。在我们的 instantiate 调用,它创建了一个新的 Artist 对象,但未提交。这就是为什么我们看到它的 guid 是该字段的一部分。
对象
该字段包含以下列表 Mendix 运行时操作需要作为响应的一部分发送给客户端的对象。在我们的示例中,运行时发送新 Artist 目的。
一个典型的 Mendix 对象可能有以下字段:
objectType:定义对象的对象类型。guid:对象的唯一ID。hash:这确保整个对象内容不被篡改。attributes:包含 提交 对象每个属性的值。对于新对象,这些值等于属性的默认值。请注意,只有当前用户可以访问的属性才会出现。
让我们在简短介绍完以下内容后回到我们的演示应用程序: xas 请求和响应。
单击新建按钮将创建“艺术家”实体的新实例,并且 Mendix 客户端显示带有新对象的详细信息页面:

现在我们可以使用状态检查快捷方式 (按Ctrl+其他+G)查看客户端状态:
{
"MyFirstModule.Artist": {
"5629499534213124 (new)": {
"subscribedWidgets": [
"MyFirstModule.Artist_NewEdit.dataView1",
"MyFirstModule.Artist_NewEdit.textBox1",
null
]
}
}
}
附庸国现在有了新的 Mendix 对象,它通过添加一个来表示它 (new) 前缀表示它尚未提交。它还有一个 subscribedWidgets 字段,表示使用该对象的小部件。这就是该对象保留在状态中的原因。最后一个条目是 null,这意味着页面上或 Mendix 客户端本身使用该对象,但没有描述。
将新艺术家的名字更改为 Pablo Picasso,然后再次检查状态:
{
"MyFirstModule.Artist": {
"5629499534213124 (new)": {
"changes": {
"FullName": {
"value": "Pablo Picasso"
}
},
"subscribedWidgets": [
"MyFirstModule.Artist_NewEdit.dataView1",
"MyFirstModule.Artist_NewEdit.textBox1",
null
]
}
}
}
}
这次我们看到有一个额外的 changes 对象的字段,其中显示了我们对 FullName 领域。
在这一刻, Mendix 客户端存储初始值和更改后的值 FullName 字段。您可以通过从控制台请求对象来检查这一点 获取数据 并比较从 获取MxObject 和 MxObject.获取原始值 方法:

点击保存此艺术家 Save 按钮,并检查它触发的请求(为清楚起见省略了一些字段):
{
"action": "commit",
"params": {
"guids": [
"5629499534213124"
]
},
"changes": {
"5629499534213124": {
"FullName": {
"value": "Pablo Picasso"
}
}
},
"objects": [{
"objectType": "MyFirstModule.Artist",
"guid": "5629499534213124",
"hash": "31doiDAq6u7/rMnqwWno01jhLkhpeQ+vKU/rI+IQor8=",
"attributes": {
"FullName": {
"value": null
}
}
}]
}
这次 commit 向运行时发出操作请求,其中包含 guid 提交到数据库的对象 params 领域。此外 changes 字段包含 Pablo Picasso 我们做出的改变,以及 objects 包含 Mendix 我们之前创建的对象。
Here is the response:
{
"commits": [
"5629499534213124"
],
"changes": {},
"resets": {
"5629499534213124": [
"FullName"
]
},
"deletes": [],
"newpersistable": [],
"objects": [{
"objectType": "MyFirstModule.Artist",
"guid": "5629499534213124",
"hash": "31doiDAq6u7/rMnqwWno01jhLkhpeQ+vKU/rI+IQor8=",
"attributes": {
"FullName": {
"value": "Pablo Picasso"
}
}
}]
}
没有一个 actionResult 这次是字段,但是状态有很多变化:
- 此
commits字段现在包含guid的Artist我们刚刚提交,所以客户端状态知道它不再是一个新对象。 - 此
resets字段包含guid的Artist并提到FullName字段更改已删除(因为现在已提交)。客户端现在可以从状态中删除此更改。
此 objects 字段现在表示已提交对象的最新版本,其中 FullName 字段值设置为 Pablo Picasso.
为什么运行时会再次返回已提交的对象?
客户端已经拥有处于其状态的对象并且可以重用它,那么运行时为什么会再次返回它? 原因有两个:
- 该对象可能有一个事件处理程序,该事件处理程序进一步修改了该对象
- 该对象可能具有计算属性,并且其值在提交时可能已发生改变
这就是为什么客户端需要对象的最新值,以及为什么它从运行时返回。
现在你的应用程序如下所示:

如果你现在检查一下这个状态,你就会发现 Pablo Picasso 仍处于状态,因为它被列表视图使用。但有一个小区别:它不再被标记为新的。
{
"5629499534213124": {
"subscribedWidgets": [
"MyFirstModule.Artist_Overview.listView1",
"MyFirstModule.Artist_Overview.textBox1"
]
}
}
此 Mendix 客户端还将客户端状态用作缓存层的一种形式。当对象处于该状态并且小部件需要它时,不会再次从运行时检索它。单击 編輯藝術家 按钮“Pablo Picasso”并勾选 网络 选项卡。您会注意到没有新的 xas 将请求检索 Pablo Picasso 从运行时获取。由于已经处于该状态,因此无需请求。
现在让我们点击 取消 在编辑页面上,点击进入应用程序主页 主页 菜单项,并检查状态:
{
"MyFirstModule.Artist": {
"5629499534213124": "Going to be garbage collected †"
}
}
大约15秒后检查状态。你会看到它是空的。
但是为什么呢?
这个问题的答案在于一个新概念:状态垃圾回收。此机制会检测并移除状态中不必要的对象,因此无论您创建或使用多少对象,我们的应用程序都能以最佳状态运行。在我们的应用程序中, Pablo Picasso 自从我们打开主页以来,对象就不再需要了,因为没有小部件显示它。这就是它从状态中删除的原因。然而,从状态中收集垃圾是一个复杂的话题,所以我将在本博客系列的第二部分中介绍它。
希望您喜欢阅读这篇文章并觉得它有用。稍后再见第 2 部分!
