“锅炉”: __B__ackend - __O__riginated __I__nstantly- __L__oaded __E__ntity __R__epository
注意:未分析或检查此软件包的内存使用情况。建议您使用Kubernetes来提高容错能力。
Flask-boiler通过Firestore管理您的应用程序状态。您可以创建视图模型来汇总基础数据源,并将其立即永久存储在Firestore中。因此,您的前端开发将像使用Firestore一样容易。 Flask-boiler与Spring Web Reactive相当。
演示:
当您更改会议中一位参与者的出席状态时,所有其他参与者都会收到出席者列表的更新版本。
您可能要使用此框架或架构实践的一些原因:
- 您想要构建一个反应式系统,而不仅仅是反应式视图。
- 您要构建一个分布式系统固有的可伸缩应用程序。
- 您需要一个具有更高抽象级别的框架,以便可以交换传输协议之类的组件
- 您希望您的代码易于阅读,清晰并主要使用python编写,同时保持对不同API的兼容性。
- 您有不断变化的需求,并且希望具有灵活性来迁移不同的层,例如,从REST API切换到WebSocket来提供资源。
该框架处于beta测试阶段 。无法保证API,并且可能会更改。
文档: 阅读文档
快速入门: 快速入门
API文档: API文档
使用烧瓶锅炉的项目示例: gravitate-backend
Boiler会将您的python代码编译为flink作业,Web服务器以及其他将在kubernetes引擎(目前尚未实现)上运行的代码。
锅炉在技术上是MVVM(Model-View-ViewModel),其中,
- 模型由事务性数据库或数据存储组成,并位于后端。
- ViewModel由一个分布式状态组成,该状态由Model和聚合器组成。它是锅炉的主要部分。对于客户端读取,它接收来自Model层的流,并将它们作为View输出到View层。对于客户端编写,它从View层接收更改流,并在Model层上操作以保留更改。 ViewModel位于后端,可以用作锅炉python代码,或者在大数据应用程序中(待实现)编译为flink作业。
- 视图是后端的表示层。它为1NF范式数据提供服务,无需进一步聚合即可读取到前端。客户端读取和写入View。 View应该是短暂的,并且可以从ViewModel重建。
视图可以是远程系统,例如。 Firestore或leancloud。
在您的项目目录中,
pip install flask-boiler
在快速入门中查看更多信息。
您可以合并在域模型中收集的信息并在Firestore中提供这些信息,以便前端可以读取单个文档或集合中所需的所有数据,而无需客户端查询和过多的服务器往返时间。
有一篇中型文章介绍了一种类似的架构,称为“ reSolve”架构。
有关如何使用flask-boiler在examples/meeting_room/view_models
中公开“视图模型”的信息,请参见examples/meeting_room/view_models
,前端可以直接查询而不进行聚合。
flask-boiler
本质上是源-接收器操作的框架:
Source(s) -> Processor -> Sink(s)
以查询为例,
- 锅炉
- NoSQL
- Flink * 静态方法 staticmethod:转换为UDF * 类方法 classmethod:转换为运算符和聚合器
class CityView(ViewModel):
name = attrs.bproperty()
country = attrs.bproperty()
@classmethod
def new(cls, snapshot):
store = CityStore()
store.add_snapshot("city", dm_cls=City, snapshot=snapshot)
store.refresh()
return cls(store=store)
@name.getter
def name(self):
return self.store.city.city_name
@country.getter
def country(self):
return self.store.city.country
@property
def doc_ref(self):
return CTX.db.document(f"cityView/{self.store.city.doc_id}")
### 文件检视
class MeetingSessionGet(Mediator):
from flask_boiler import source, sink
source = source.domain_model(Meeting)
sink = sink.firestore() # TODO: check variable resolution order
@source.triggers.on_update
@source.triggers.on_create
def materialize_meeting_session(self, obj):
meeting = obj
assert isinstance(meeting, Meeting)
def notify(obj):
for ref in obj._view_refs:
self.sink.emit(reference=ref, snapshot=obj.to_snapshot())
_ = MeetingSession.get(
doc_id=meeting.doc_id,
once=False,
f_notify=notify
)
# mediator.notify(obj=obj)
@classmethod
def start(cls):
cls.source.start()
### WebSocket视图
class Demo(WsMediator):
pass
mediator = Demo(view_model_cls=rainbow_vm,
mutation_cls=None,
namespace="/palette")
io = flask_socketio.SocketIO(app=app)
io.on_namespace(mediator)
您可以使用RestMediator创建REST API。当您运行_ = Swagger(app)
时,OpenAPI3 docs将在<site_url>/apidocs
自动生成。
app = Flask(name)
class MeetingSessionRest(Mediator):
# from flask_boiler import source, sink
view_model_cls = MeetingSessionC
rest = RestViewModelSource()
@rest.route('/<doc_id>', methods=('GET',))
def materialize_meeting_session(self, doc_id):
meeting = Meeting.get(doc_id=doc_id)
def notify(obj):
d = obj.to_snapshot().to_dict()
content = jsonify(d)
self.rest.emit(content)
_ = MeetingSessionC.get(
doc_id=meeting.doc_id,
once=False,
f_notify=notify
)
# @rest.route('/', methods=('GET',))
# def list_meeting_ids(self):
# return [meeting.to_snapshot().to_dict() for meeting in Meeting.all()]
@classmethod
def start(cls, app):
cls.rest.start(app)
swagger = Swagger(app)
app.run(debug=True)
(目前正在实施中)
使用cls.new
创建的对象->使用obj.to_view_dict
导出的对象。
在数据库中创建新域模型时创建的对象->基础数据源更改时对象更改->对象调用self.notify
数据流方向描述为源->接收器。 “读取”描述了前端在Sink中发现有用数据的数据流。 “写入”描述了数据流,其中接收器是真实的唯一来源。
读取:请求->响应 写入:请求->文档
- 前端向服务器发送HTTP请求
- 服务器查询数据存储
- 服务器返回响应
读取:文档->文档 写入:文档->文档
- 数据存储区触发更新功能
- 服务器重建可能会因此而更改的ViewModel
- 服务器将新建的ViewModel保存到数据存储区
读取:文档->文档 写入:文档->文档
- 数据存储在时间
t
触发文档d
更新功能 - 服务器开始交易
- 服务器将write_option设置为仅在文档最后一次更新时间为
t
时才允许提交(仍在设计中) - 服务器使用事务构建ViewModel
- 服务器使用事务保存ViewModel
- 服务器将文档
d
标记为已处理(删除文档或更新字段) - 如果前提条件失败,服务器将从步骤2重试,最多重试次数由MAX_RETRIES定义
读取:文档-> WebSocket事件 写入:WebSocket事件->文档
- 前端通过向服务器发送WebSocket事件来订阅ViewModel
- 服务器将侦听器附加到查询结果
- 每次查询结果更改并保持一致时: 1.服务器重建可能会因此而更改的ViewModel 2.服务器发布新建的ViewModel
- 前端结束会话
- 文档监听器被释放
读取:文档->文档\写入:文档->文档
REST | 查询 | 查询+任务 | WebSocket | 数据库文档 | |
---|---|---|---|---|---|
Guarantees | ≤1 (At-Most-Once) | ≥ 1 (At-Least-Once) | =1[^1] (Exactly-Once) | ≤1 (At-Most-Once) | ≥ 1 (At-Least-Once) |
Idempotence | If Implemented | No | Yes, with transaction[^1] | If Implemented | No |
Designed For | Stateless Lambda | Stateful Container | Stateless Lambda | Stateless Lambda | Stateful Container |
延迟 | Higher | Higher | Higher | Lower | Higher |
吞吐量 | Higher when Scaled | Lower[^2] | Lower | Higher when Scaled | Lower[^2] |
有状态的 | No | If Implemented | If Implemented | Yes | Yes |
Reactive | No | Yes | Yes | Yes | Yes |
消费者可以成功提交更改并将事件标记为已处理。
使用Firebase Firestore有时需要跨多个文档重复字段,以便查询数据并在前端正确显示它们。烧瓶锅炉通过将域模型和视图模型分离来解决此问题。视图模型随域模型的更改而自动生成和刷新。这意味着您只需要在域模型上编写业务逻辑,而不必担心数据将如何显示。这也意味着视图模型可以直接显示在前端,同时支持Firebase Firestore的实时功能。
无需为数据库和其他云服务配置网络和不同的证书设置。您要做的就是在Google Cloud Console上启用相关服务,并添加证书。 Flask-boiler配置所需的所有服务,并将它们作为整个项目中的单例上下文对象公开。
由于所有视图模型都保留在Firebase Firestore中。即使您的应用程序实例处于脱机状态,用户仍然可以从Firebase Firestore访问数据视图。每个视图也是烧瓶视图,因此,如果Firebase Firestore不可行,您还可以使用自动生成的REST API访问数据。
通过将业务数据与前端可访问的文档分开,您可以更好地控制显示哪些数据,具体取决于用户的角色。
所有ViewModel都有自动生成的文档(由Flasgger提供)。这有助于AGILE团队使他们的文档和实际代码保持同步。
当您需要更好的性能或关系数据库支持时,您始终可以通过添加诸如flask-sqlalchemy
模块来重构特定层。
在GraphQL中,每次查询都会对字段进行评估,但仅当基础数据源发生更改时,flask-boiler才会对字段进行评估。这样可以更快地读取一段时间未更改的数据。此外,由于读取了在一次交易中对Firestore进行的所有更改之后触发了字段评估,因此数据源也将是一致的。
但是,GraphQL允许前端自定义返回值。您必须在烧瓶锅炉中定义要返回的确切结构。尽管如此,它还是有优势的,因为大多数请求和响应文档都可以与REST API相同的方式完成。
REST API不会缓存或存储响应。当烧瓶锅炉评估视图模型时,响应将永久存储在Firestore中,直到更新或手动删除为止。
Flask-boiler通过与Firestore集成的安全规则来控制基于角色的访问。 REST API通常使用JWT令牌控制这些访问。
Redux主要在前端实现。 Flask-boiler以后端为目标,并且具有更高的可扩展性,因为所有数据都与Firestore(无限扩展的NoSQL数据存储)进行通信。
Flask-boiler是声明性的,而Redux是必须的。 REDUX的设计模式要求您在域模型中编写函数式编程,但是flask-boiler支持另一种方法:ViewModel从域模型读取和计算数据,并将该属性公开为属性获取器。 (在写入DomainModel时,视图模型会更改域模型,并将操作公开为属性设置器)。尽管如此,您仍然可以添加在域模型更新后触发的函数回调,但是这可能会引入并发问题,并且由于在烧瓶锅炉中进行设计折衷,因此无法完美支持。
欢迎提出请求。
请确保适当更新测试。