A async Domain-Drive-Desgin support API develop framework,一个异步的,支持领域驱动开发的应用程序接口开放框架。


Keywords
asyncio, domain-driven-design, httptools, python, restful, web
License
GPL-2.0+
Install
pip install SynicDomain==0.0.6.1

Documentation

SynicDomain

A async Domain-Drive-Desgin support API develop framework

一个异步的,支持领域驱动开发的应用程序接口开发框架。


安装与实例

本框架支持Python3.6及以上版本

源码安装

git clone https://github.com/TaylorHere/SynicDomain.git
#查看实例
cd SynicDomain
python3.6 main.py

pip安装

pip3 install SynicDomain

简单实例

from SynicDomain import APP, Handler, Middware, Async_Task, SQLalchemyView
#导入相关模块

app = APP()
#初始化一个SynicDomain App

@app.url('/', ['after_handle', 'login', 'before_handle', 'create_log'])
#将下面的类与url '/' 进行绑定,并且当用户访问这个url的时候
#按照after_handle、login、before_handle、create_log这样的顺序
#启动自己或其他endpoint
@app.endpoint
#声明下面的类是一个endpoint,endpoint可以与url进行绑定
class login(Handler):
    #一个处理登陆事务的类,其父类为Handler
    async def core(self, context, request):
    #一个异步方法叫做core,其参数context是一个信息结构体,一个RequestContext的实例
    #参数request是当次访问的请求对象
        name = request.header('name')
        #获取head
        return name
        #返回信息会自动添加到context中
        #给结构体添加一些信息

if __name__ == '__main__':
    app.run()
    #启动服务

开发目的

SynicDomain是对领域驱动api构建方案的一次绝妙尝试

在实际工作中我们往往会使用领域式方法去分析业务,比如使用思维导图描述业务,然后再由程序员将他们转化为数据库表并映射成RESTful或其他风格的api,最后加上领域驱动的事件处理代码,最后供前端工程师去使用。

SynicDomain想做的事情就是将程序员、PM、客户在交流期间产生的领域模型直接构建成可用的、基本符合业务的api。

我们将会提供一个,供程序员、PM、客户共同使用的思维导图工具,在这个工具里,大家可以使用思维导图类的工具描述具体到api节点,字段,字段检测, 领域事件定义的业务逻辑。

并且它会直接生成一个可用的api,甚至还有管理站点。 我们的原理是,通过规范思维导图节点内容,达成一种编码范式,在这种范式的基础上,我们能把该描述文件做为python或其他语言和框架的web程序的配置文件,并根据这些配置文件产生具体的模块等。

后期我们甚至会将模块docker化,然后进一步规范描述文件的编码范式,从而实现microservices

方案简述

如果你阅读SynicDomain的代码你会发现SynicDomain和Sanic十分相似。

是的,SynicDomain很多地方都借鉴了Sanic甚至连名称SynicDomain都是从Sanic演变过来的,但是这个项目与Sanic毫无关系

整个项目主要依赖于asyncio这个Python3.6以后才加入的内部库,借助其高效的协程管理能力和自带的sock监听整个项目主要核心部分在于Server.py文件中的一下几个代码片段。

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

loop = asyncio.get_event_loop()

protocol = partial(
        Protocol_Factory,
        loop,
        request_dispatcher,
        default_request_time_out
    )
server = loop.run_until_complete(
    loop.create_server(protocol, host, port))
    loop.run_forever()

这部分代码的大意是,获取一个事件loop,并且使用uvloop做代理,最后一行使用loop的create_server创建一个处理TCP链接的Server,具体的处理方案由一个 Protocol_Factory的工厂类来处理,在这个工厂类里面,使用了httptools来解析http包,并将解析结果以task的形式交由 request_dispatcher,在这个request_dispatcher中构建了我们整个框架的运行逻辑。


框架逻辑

框架将每个Request视为一个任务,在每个任务中定义的很多事件,这些事件串起来组成一个事件链,事件链再和URL绑定,这样,当一个URL被触发的时候就会去触发这个事件链条,负责处理各个事件链中的具体事件的对象被称为Handler,他们有以下类型:

  1. Handler 处理器
  2. Middware 中间层
  3. Asnyc_task 异步任务
  4. Resource 资源

1、2、3的定义如下:

class Handler(object):
    """
        the handler, whicth will join the handler chian, and when it‘s the last handler in the handler chain,
        the response function weil be runned,if not ,the pass_by function runned.
    """
    async def core(self, context, request):
        return 'this is a handler'

    async def response(self, context, request):
        return await self.core(context, request)

    async def pass_by(self, context, request):
        return await self.core(context, request)


class Async_Task(object):
    """
        the async task, whicth will join the handler chian, bur run like a async task,
        it will not block the behind handler'running.
    """
    async def core(self, context, request):
        return 'this is a Async_task'

    async def pass_by(self, context, request):
        await self.core(context, request)


class Middware(object):
    """
        the middware, whicth will join the handler chian,but not work like response
        and if the middware is the last middware in the handler chian,it will also be
        runned, even the response has sent.
    """
    async def core(self, context, request):
        return 'this is a middware'

    async def pass_by(self, context, request):
        return await self.core(context, request)

当一个Handler类型为事件链上最后一个Handler时,Handler对象的Response方法会被返回,其他情况均触发pass_by方法。

middware和Asnyc_task非常相似,他们都是不处理返回,用来处理一些任务,但是Asnyc_task将会一个异步运行,middware将会按照事件链上声明的顺序运行。


注册事件链

需要通过以下顺序来注册一个事件链

  1. 实现一个具体Handler子类
  2. 将它声明为endpoint
  3. 将url与endpoints绑定 app提供两个包装器来实现后两步
@app.url('/', ['after_handle', 'login', 'before_handle', 'create_log'])
@app.endpoint
class login(Handler):
    async def core(self, context, request):
        pass

@app.endpoint
class after_handle(Middware):
    async def core(self, context, request):
        pass

@app.endpoint
class before_handle(Middware):
    async def core(self, context, request):
        pass

@app.endpoint
class before_handle(Asynic_Task):
    async def core(self, context, request):
        pass

每个@app.endpoint是在将位于其下方的那个class声明为一个endpoint

每个@app.url是用来将url参数,与一个包含endpoint名称字符串的列表绑定在一起,一般情况下我们将url放在Handler上方

当访问@app.url所声明的url时,其后方的endpoints列表被视为这个url的绑定事件,即,该url所代表的镞及其子领域。

一般情况下不推荐在代码中产生事件,每一个代码块响应和产生的事件都应该使用edpoints列表显示表现出来。

或许你注意到了每个handler的都有几个处理函数,每个处理函数里面都有一个context和request对象


handler的处理函数以及context对象和request对象

一个handler一定有这个方法

    def pass_by(self, context, request)

在事件链条上,除了最后一个handler不会被调用,其他handler都会调用这个方法

意义是,该endpoint被调用,但该endpoint不会发送Response,只是负责参与一些运算

    def response(self, context, request)

也就是response这个函数是在该handler作为响应handler时被调用

为了将响应和运算分开,同时又能让大部分代码重用,所以引入一个叫core的方法来负责重用,response和pas_by默认调用core方法。

    def core(self, context, request)

当然你完全可以选择无视core。

接下来说context和request

每个http request会被解析成一个request对象,request对象里有关于http request的header,body,url,method等信息,并提供了将json,form等解析成字典的功能

每次处理一个reqeust时,会将该request对象放在endpoints列表上按顺序执行,为了能在链条上传递信息,所以引入了RequestContext,即每个处理函数里面的context,请求的上下文的默认行为是将每个handler的返回与handler的名字一起放入一个字典里,当你拿到context后,可以按照context['before_handle'],这样的方法将before_handel的处理数据拿出来使用,你可以将context打印出来查看其内部结构。 如下

Request Context Info
============================================
        {'before_handle': None, 'User': <SynicDomain.Response.HTTPResponse object at 0x109280ee8>}