NetKernel是一个软件系统,将REST和Unix的基本属性组合成一个强大的抽象,称为面向资源的计算(ROC)。 面向资源的计算的核心是将信息的逻辑请求(资源)与提供信息的物理机制(代码)分开。 与其他方法相比,使用ROC构建的应用程序被证明是小型,简单,灵活的,并且所需的代码更少。
你好,世界!
在面向资源的系统中,请求资源,并返回不可变的具体资源表示形式。 这直接遵循REST和万维网的原理。 在Web中,域名服务(DNS)将对诸如http://www.1060research.com之类的资源的请求解析为端点IP地址。 该请求将发送到该IP地址,然后Web服务器将发送一个响应,其中包含资源的具体,不变的表示形式。 类似地,在NetKernel中,资源请求被解析为一个称为Accessor的端点,该端点负责返回具体的,不变的表示形式。 NetKernel是用Java实现的,因此我们将在第一个示例中使用该语言,但是,稍后我们将展示Java是许多受支持的语言之一。
将请求传递到已解析的访问器的processRequest
方法。 下面的示例创建一个不可变的StringAspect,其中包含“ Hello World”消息,并在响应中将其返回:
public void processRequest(INKFConvenienceHelper context) throws Exception
{ IURAspect aspect = new StringAspect("Hello World");
INKFResponse response = context.createResponseFrom(aspect);
context.setResponse(response);
}
上下文对象是微内核的接口,它允许端点在逻辑请求和物理代码之间桥接。 当逻辑请求的URI解析到此端点时,将触发processRequest方法。 与Java Servlet的URI请求只能来自Web的情况不同,解析为NetKernel访问器的URI请求可以来自任何地方,包括其他端点。
如果Servlet需要其他信息,它将使用对其他Java对象的引用,例如在使用JDBC访问数据库时创建深度调用堆栈。 这是ROC开始和Web结束的地方。 访问器没有对其他访问器的内存引用。 为了获得更多信息或调用其他软件服务,它们将子请求发布到逻辑资源地址空间中。
在NetKernel中,资源也由URI地址标识,就像在万维网中一样。
现在,我们离开Java对象的物理级别,看看如何定义URI地址以及如何将其动态绑定到我们的代码。 NetKernel是一种模块化体系结构。 软件资源可以打包在称为模块的物理容器中。 模块不仅是用于部署的物理包,还定义了资源的逻辑地址空间及其与物理代码的关系。 模块就像一个完全独立的微型万维网软件。 模块定义中的以下条目将逻辑地址“ ffcpl:/ helloworld”映射到我们的访问器HelloWorld对象。
<ura>
<match>ffcpl:/helloworld</match>
<class>org.ten60.netkernel.tutorial.HelloWorld</class>
</ura>
请求和端点之间的绑定仅在请求被解决时发生。 访问者完成后,关系就解耦了。 这与Java和其他语言(绑定一旦建立便是永久性的)不同。 这种动态逻辑绑定产生了非常灵活的系统,可以在运行时对其进行重新配置。
在逻辑地址空间中,我们有了新的自由度。 我们可以将请求从逻辑映射到逻辑,将逻辑映射到物理(如上所述),或者从一个地址空间映射到另一个地址空间。 以下重写规则说明了我们可以使用正则表达式匹配将请求的URI更改为另一个:该规则的作用是将一个请求(例如“ ffcpl:/ tutorial / helloworld”)映射到“ ffcpl:/ helloworld”。
<rewrite>
<from>ffcpl:/tutorial/(.*)</from>
<to>ffcpl:/$1</to>
</rewrite>
模块是封装的专用逻辑地址空间。 一个模块可以导出一个公共地址空间,然后可以由其他模块导入该地址。 在我们的示例中,我们的模块导出公共地址空间“ ffcpl:/ tutorial /.*”,承诺提供位于教程路径下的所有资源。
<export>
<uri>ffcpl:/tutorial/(.*)</uri>
</export>
到目前为止,我们还没有考虑请求的来源。 没有初始起点,系统中将不会发生任何事情。 传输是检测(外部)事件的端点。 传输检测到事件时,它会创建一个根请求并将其发出到逻辑地址空间中,并等待对端点进行定位,绑定,安排进行处理并返回表示形式。 一个模块可以托管任何数量的传输,每个传输都将其根请求注入到托管模块中。 例如,HTTP传输检测到HTTP请求的到达,然后创建并发出相应的内部NetKernel根请求。 下图说明了HTTP请求转换为NetKernel请求并传递到我们的HelloWorld访问器类的路径。
http://localhost:8080/tutorial/helloworld (at the transport)
|
v
ffcpl:/tutorial/helloworld (request issued by transport)
|
v
ffcpl:/tutorial/helloworld (request that enters our module)
|
v
ffcpl:/helloworld (after rewrite rule)
|
v
org.ten60.netkernel.tutorial.HelloWorld (Physical Accessor code)
系统中没有任何内容决定对Hello World资源的请求如何产生。 它的地址空间可以同时连接到HTTP,JMS,SMTP,LDAP等,您可以将其命名为-外部应用程序协议,甚至是我们软件体系结构的其他层。 NetKernel系统中的每个模块都是一个独立的封装的面向资源的子系统。
地址和地址空间
在NetKernel中,模块定义了专用地址空间。 专用地址空间中只能发生三件事:
- 将逻辑地址映射到逻辑地址
- 将逻辑地址映射到物理端点
- 从另一个模块导入逻辑地址空间
有了这三个主要关系,就可以制作出清晰的分层和通道化架构。 由于这些关系是动态的,因此架构本身也是动态可组合的。 下图显示了典型NetKernel应用程序的高级视图。 传输将根请求发送到托管模块的专用地址空间中,然后NetKernel搜索将处理该请求的终结点。 在我们的示例中,托管模块导入模块“ A”,“ B”和“ C”。 每个都将其公共导出的地址空间添加到主机模块的专用地址空间,特别是ffcpl:/accounting/.*
ffcpl:/payroll/.*
和ffcpl:/crm/.*
。
如果在我们的示例中,针对URI ffcpl:/crm/contacts
发出了根请求,则该根请求将与模块“ C”的导出地址空间匹配,并且该请求将发送至该模块,最终被解析为可以满足以下要求的物理代码:请求ffcpl:/crm/contacts
,可能是通过使用诸如JDBC连接之类的物理对象,或更常见的是通过发出对模块“ C”范围内可用的逻辑关系数据库服务的子请求。
存取器
接下来,我们切换回物理级别,并仔细查看访问器。 如我们所见,访问器是返回资源表示形式的端点。 存取器本身非常简单。 他们只能做四件事:
- 解释正在请求什么资源(即通过检查发起请求)
- 创建并发出子请求以获取其他信息(作为同步或异步请求)
- 通过执行服务来创造价值-做点事!
- 创建并返回一个不变的物理资源表示
访问者从其上下文中发现被要求执行的操作。 可以检索请求URI以及命名参数的参数值。 如果多个地址映射到单个端点,则访问者可能需要知道当前请求使用了哪个URI。 通过逻辑/物理隔离,一段代码可能具有映射到它的多个逻辑位置。
使用active:
URI方案使用命名参数调用服务。active方案采用以下形式:
active:{service-name}+{parameter-name}@{uri-address}
每个活动URI指定一个服务和任意数量的命名参数。 例如, toUpper
服务采用一个名为operand
参数,并返回由作为参数提供的URI标识的资源的大写转换。
active:toUpper+operand@ffcpl:/resources/message.txt
以下BeanShell脚本实现了toUpper
服务。 它使用带有URI this:param:operand
的sourceAspect
方法检索“操作数”资源的不可变方面。 我们可以使用上下文对象来获取调用请求,查找命名参数“ operand”,获取其URI并对该资源发出子请求。 相反,NetKernel Foundation API为请求的参数提供了本地内部逻辑地址空间。 通过请求URI this:param:operand
我们实际上是在要求取消对操作数指针的引用。
import org.ten60.netkernel.layer1.representation.*;
import com.ten60.netkernel.urii.aspect.*;
void main()
{
sa=context.sourceAspect("this:param:operand",IAspectString.class);
s=sa.getString();
sa=new StringAspect(s.toUpperCase());
resp=context.createResponseFrom(sa);
resp.setMimeType("text/plain");
context.setResponse(resp);
}
该脚本指定它希望返回操作数资源作为IAspectString
接口的实现。 但是,在逻辑级别,代码不了解物理级别的类型。 这导致了一个新的概念,称为transrepresentation 。 如果客户端请求端点不提供的表示类型,则微内核可以处于中间状态。 当检测到不匹配时,微内核会搜索可以从一种类型转换为另一种类型的Transreptor 。
事实证明转发器非常有用。 从概念上讲,转发器将信息从一种物理形式转换为另一种物理形式。 这涉及大量的计算机处理,包括:
- 对象类型转换
- 对象结构转换
- 解析中
- 编译中
- 序列化
关键是这是一种无损的转换,在更改物理表示时将保留信息 。 转发器通过从逻辑级别隐藏物理级别的详细信息来帮助降低开发人员的复杂性,使开发人员可以专注于重要的信息。 例如,诸如active:xslt
类的服务以DOM的形式请求信息,而在逻辑级别工作的开发人员则提供了资源引用,该引用在物理级别上是包含XML的文本文件。 NetKernel会自动搜索可以将文本XML表示转换(解析)为DOM表示的转发器。 转发的体系结构和设计意义在于类型去耦以及增加的应用程序和系统灵活性。
另外,转发允许系统将信息从低效率的形式转换为可有效处理的形式,例如从源代码转换为字节代码。 这些转换频繁发生,但仅需一次转换成本,此后可以有效形式获得。 从正式的意义上讲,透明删除了资源中的熵。
资源模型
我们已经看到了如何将逻辑级别URI地址解析为物理级别的终结点,并在处理所需的时间上绑定到该终结点。 我们已经看到,诸如类型之类的物理层问题可以在物理层中隔离。 我们还看到可以使用命名参数调用服务,所有参数均编码为URI地址。
这导致了资源模型 ,物理资源表示类型(对象模型)和相关服务(访问器)的集合的思想,这些模型一起提供围绕特定信息形式的工具集,例如二进制流,XML文档,RDF图,SQL语句和结果集,图像,JMS消息,SOAP消息等。资源模型的思想使开发人员可以结合一个或多个资源模型来构建复合应用程序,从而呼应Unix哲学,即Swift拥有特殊的可重用工具。结合在一起创建解决方案。
图像资源模型包括诸如imageCrop
, imageRotate
, imageDither
等服务。 使用图像资源模型,开发人员可以创建图像处理管道,所有这些管道都具有简单的请求,例如:
active:imageCrop+operator@ffcpl:/crop.xml+operand@http://1060research.com/images/logo.png
NetKernel的XML资源模型包括转换语言,几种验证语言和许多其他XML技术。 除此之外,PiNKY提要处理工具箱是XML资源模型的一种特殊功能,它支持ATOM,RSS和许多简单的提要操作,并且与XML资源模型100%向下兼容。 使用转发器,开发人员无需知道XML资源在物理上是DOM,SAX流还是几种可能的表示类型之一。 使用XML资源模型,开发人员可以快速构建XML处理系统。 例如,下面的请求使用XSLT服务来转换资源ffcpl:/data.xml
与样式表资源ffcpl:/style.xsl
:
active:xslt+operator@ffcpl:/style.xsl+operand@ffcpl:/data.xml
排序
资源请求URI本质上是面向资源的计算模型的“操作码”。 就像Java字节码一样,它们通常级别太低,很难手动编码。 而是可以使用多种脚本语言来定义和发出这些请求。 我们之前看到的上下文对象是一个称为POSIX的示例,它类似于微内核周围称为NetKernel Foundation API的抽象。 该API可用于所有受支持的动态过程语言。 另外,提供了专门的声明性语言,其目的仅在于定义和发出源请求。
一种这样的脚本语言是DPML,它是一种使用XML语法的简单语言。 为什么使用XML语法? 因为在动态松耦合系统中,代码是与其他资源一样的资源,所以直接创建动态生成代码的过程非常简单。 XML语法是用于代码生成的简单输出格式。 为了提供DPML的味道,以下指令请求与上一节相同的XSLT转换,每个“ instr”都对应一个活动的URI请求,每个“ target”是对另一个资源的分配。 URI this:response
用作约定,以指示脚本要返回的资源。
<instr>
<type>xslt</type>
<operator>ffcpl:/style.xsl</operator>
<operand>ffcpl:/data.xml</operand>
<target>this:response</target>
</instr>
从这个基础上,很容易解释下面的DPML程序,该程序在两个请求中从数据库创建HTML页面:
<idoc>
<instr>
<type>sqlQuery</type>
<operand><sql>SELECT * from customers;</sql></operand>
<target>var:result</target>
</instr>
<instr>
<type>xslt</type>
<operand>var:result</operand>
<operator>ffcpl:/stylepage.xsl</operator>
<target>this:response</target>
</instr>
</idoc>
在NetKernel中,语言运行库是服务。 像任何其他服务一样,它们是无状态的,并且在将程序代码作为状态传输时执行程序的执行。 这与物理意义上的传统软件观点大不相同,在传统的软件观点中,语言位于信息的前面,而不是对信息起促进作用。 例如,要使用Groovy语言运行时服务,以下请求将提供ffcpl:/myprogram.gy
资源,其中包含程序作为请求的状态。
active:groovy+operator@ffcpl:/myprogram.gy
NetKernel支持多种语言,包括BeanShell,Groovy,Ruby,JavaScript,Python,DPML XML语言(例如XQuery)以及动态编译的Java。 Java虚拟机上运行的任何语言都可以集成到NetKernel中,包括工作流引擎等自定义语言。
模式
ROC在逻辑级别上提出了一组新的体系结构设计模式。 让我们看两个示例,Mapper和GateKeeper。
映射器模式是一种将有限的资源请求集定向到单个物理代码点的方法。 在这种模式下,将一个空间中对资源的请求映射到物理代码,该物理代码将每个请求解释并重新发出到映射的地址空间中。 映射器将第二个映射请求的响应作为第一个请求的结果返回。
这种模式有多种变体,一种称为active:mapper
服务使用的资源包含地址空间之间的路由映射。 另一个示例是Gatekeeper,它用于为进入地址空间的所有请求提供访问控制。 仅当有足够的凭据可用于验证请求时,网闸才会接受请求。
映射器模式的所有变体可以透明地分层放置在任何应用程序地址空间上。 此模式的其他用途包括审核,日志记录,语义和结构验证以及任何其他适当的约束。 这种模式的特别优势在于,可以在应用程序中引入它而不会干扰其体系结构设计。
由于NetKernel中软件之间的关系是逻辑链接和动态解决的,因此请求的拦截和转换是完全自然的模型。 逻辑地址空间以非常统一的方式展现了在专业物理层技术(例如AOP)中发现的所有特征。
应用开发
使用ROC构建应用程序很简单。 如果需要任何新的物理级别功能,例如新的资源模型,则将构造必要的访问器,转发器等。 然后在逻辑级别上,通过识别和聚集资源来组成应用程序。 最后,应用约束 ,例如请求限制,安全GateKeepers和数据验证。
ROC的三个“ C”(构造,组成,约束)按该顺序应用。 可以颠倒该顺序以进行更改-可以解除约束以显示组成的应用程序并允许更改,然后可以重新应用约束。 这与面向对象的编程不同,在面向对象的编程中,约束是最初的考虑因素-类固有地在其对象的使用以及它们所包含的信息上施加了约束。 面向对象的物理系统中信息结构的更改引发了一系列事件-重新编译,分发,系统重新启动等。在NetKernel系统中,所有这些都是不必要的。 与逻辑系统的灵活性相比,物理级面向对象的系统显得脆弱。
应用架构
使用物理级别技术设计的系统通常依赖于驻留在体系结构各个级别上的可变对象。 例如,存在诸如Hibernate之类的对象关系映射技术来创建对象层,其状态与RDBMS管理的持久性存储的状态相匹配。 在这种设计中,将更新应用于对象,并且映射层负责将这些更改迁移到关系数据库。
使用ROC,所有表示都是不可变的。 这立即导致两个体系结构后果-首先,对不可变对象进行缓存可以显着提高性能(稍后会详细介绍),其次,不能更新不可变对象-必须将其无效并重新请求。
尽管可以用ROC实现许多有效的应用程序体系结构,但通常会使用数据通道方法。 在许多设计中,应用程序由逻辑分层的地址空间组成,并且垂直穿过这些层是应用程序信息的独立读写通道。 这些频道可能具有ffcpl:/customers
或ffcpl:/register-user
。
在下图中,集成层将来自不同来源的信息形式转换为通用结构。 读取的信息通道支持诸如ffcpl:/customers
资源,这些资源返回所需信息的表示形式。 在写通道中,诸如ffcpl:/register-user
类的URI寻址服务做两件事,首先,它们更新持久性存储,并且使依赖于更新信息的所有缓存资源表示形式无效。 对于习惯于OR映射方法的开发人员(例如使用Hibernate),这似乎很奇怪。 实际上,这是一个简单,优雅且高性能的解决方案。
性能
到现在为止,您必须考虑到ROC系统将花费更多的时间运行抽象而不是实际工作。 但是,与直觉相反,ROC抽象产生了明显的性能优势。
快取
由于每个资源都由URI地址标识,并且请求资源的结果是不可变的资源表示形式,因此可以使用URI地址作为缓存键来缓存任何计算出的资源。 除了计算的资源外,NetKernel的缓存还存储有关资源依赖性和计算每个缓存项的成本的元信息。 高速缓存使用依赖项信息来保证高速缓存的资源是有效的,只要它依赖的所有资源也都有效。 如果资源变为无效,则其缓存的表示形式和所有从属资源表示形式都将自动失效。
NetKernel使用存储的计算成本信息来指导它保留动态的最佳资源集-系统当前工作集中的资源根据使用频率和从缓存中弹出而需要重新计算的成本来判断是否有价值。 NetKernel缓存的操作结果是系统地消除了许多冗余计算。 来自操作系统的经验证据表明,常规业务应用程序中的缓存通常满足30%至50%的资源请求。 在以只读为主的应用程序的限制下,这可以提高到接近100%,从而使动态系统具有伪静态性能。 此外,随着系统负载的特性随时间变化,缓存将重新平衡,从而保留当前最有价值的资源。
使用CPU核心扩展
如引言中所述,ROC的本质是将逻辑信息过程与其物理实现分开。 每个对逻辑资源的请求最终都必须分配给一个物理线程以执行。 实现ROC系统的微内核可以最佳地利用计算硬件,因为它反复调度可用线程来执行每个逻辑请求。 本质上,逻辑信息系统是在可用CPU内核之间实现负载平衡的。
异步性
ROC本质上是异步的。 NetKernel Foundation API提供了一个看似同步的模型,但是微内核实际上在内部异步调度了所有请求。 因此,开发人员可以在顺序同步代码的逻辑清晰性下进行思考,同时透明地获得跨广泛的多核体系结构扩展应用程序的能力。
另外,访问器可能被明确标记为线程安全与否,从而向微内核发出信号,告知其是否可以调度并发请求。 这样就可以采用和集成库以及其他第三方贡献,而不必担心会出现不可预测的结果。
摘要
NetKernel完全不同。 不是为了创建另一个技术堆栈,而是为了采用一套简单的核心原理(Web,Unix和集合论的原理)并将其外推到一个一致的信息处理系统中。 实际上,NetKernel的起源就是这样一个问题:“ Web的经济特性可以转移到软件系统的细粒度性质上吗?”
从在惠普实验室成立之初到公司企业体系结构,乃至作为关键Web基础架构的下一代平台(Purl.org),NetKernel的生产过程都经过了近八年的硬化。 NetKernel已被证明完全是通用的,可以像其他任何应用程序一样轻松地应用于数据集成,消息处理或Web /电子邮件Internet应用程序。
参考资料
- 1060研究
- 有关面向资源的计算的白皮书: http : //www.1060research.com/netkernel/whitepapers/
- NetKernel 下载