#REST API

0 关注者 · 67 帖子

表述性状态传递 (REST) 是一种软件架构风格,它定义了一组用于创建 Web 服务的约束。符合 REST 架构风格的 Web 服务(称为 RESTful Web 服务 (RWS))提供了互联网上计算机系统之间的互操作性。RESTful Web 服务允许请求系统通过使用统一预定义的无状态操作集来访问和操作 Web 资源的文本表示。其他类型的 Web 服务(如 SOAP Web 服务)需要公开它们自己的专有操作集。

了解更多信息

文章 Hao Ma · 一月 15, 2021 3m read

介绍 

目前,诸多应用程序通过开放授权框架(OAuth)来安全、可靠、高效地访问各种服务中的资源。InterSystems IRIS目前已兼容OAuth 2.0框架。事实上社区有一篇关于OAuth 2.0和InterSystems IRIS的精彩文章,链接如下。 

然而,随着API管理工具的出现,一些组织开始将其用作单点身份验证,从而防止未经授权的请求到达下游服务,并将授权/身份验证复杂性从服务本身分离出来。 

您可能知道,InterSystems已经推出了自己的API管理工具,即InterSystems API Management (IAM),以IRIS Enterprise license(IRIS Community版本不含此功能)的形式提供。这里是社区另一篇介绍InterSystems AIM的精华帖。 

这是三篇系列文章中的第一篇,该系列文章将展示如何在OAuth 2.0标准下使用IAM简单地为IRIS中的未经验证的服务添加安全性。 

第一部分将介绍OAuth 2.0相关背景,以及IRIS和IAM的初始定义和配置,以帮助读者理解确保服务安全的整个过程。 

本系列文章的后续部分还将介绍两种使用IAM保护服务的可能的场景。在第一种场景中,IAM只验证传入请求中的访问令牌,如果验证成功,则将请求转发到后端。在第二种场景中,IAM将生成一个访问令牌(充当授权服务器)并对其进行验证。 

0
0 539
文章 Hao Ma · 一月 15, 2021 2m read

什么是npm-iris? 

NPM是“No Project Mess(项目不乱)”的缩写。 

NPM是使用Intersystems IRIS和Bootstrp 4建成的项目和任务管理应用程序。 

NPM的创建初衷是通过一个简单直观的项目和任务管理软件,帮助开发者和小型商业公司降低日常问题的复杂度。 

它能提供不同的任务视图,包括电子表格、看板、日历,甚至甘特图! 

为什么? 

在不同的团队中工作,您会发现不同的人喜欢不同的工具。 

所以,有时您会在一个项目中使用甘特图,在另一个项目中使用看板,在其他项目中使用纸上的列表…… 

NPM专注于任务。无论您和您的团队喜欢以哪种方式查看。只需单击并更改您的视图。 

功能 

  • 初始安装 
  • 项目 
  • 用户 
  • 任务 - 创建和管理任务 
  • 调度程序 - 任务的日历视图 
  • 看板 - 用看板风格管理您的任务 
  • 甘特图 - 使用甘特图查看截止日期、里程碑和进度 

新特性/改进的路线图 

  • OAuth2身份验证 
  • 项目/团队/用户的安全性 
  • 时间跟踪 
  • 自定义日历(假期) 
  • 支持附件 
  • 利用AppS.REST框架 
  • Vue.js版本 
  • Home面板,可以查看活动的概况 

 

试一下这款应用程序! 
http://npm-iris.eastus.cloudapp.azure.com:52773/npm/home.csp  

0
0 148
文章 Hao Ma · 一月 15, 2021 6m read

假设您想编写一些真正的web应用程序,例如medium.com网站的简单克隆。这类应用程序可以在后端使用任何不同的语言编写,也可以使用前端的任何框架编写。编写这样一个应用程序有很多方法,你也可以看看这个项目。它为完全相同的应用程序提供了一堆前端和后端实现。您可以轻松组合它们,任何所选前端应该与任何后端搭配。

我来介绍一下这个使用后端InterSystems IRIS来实现后端的相同的应用程序。  

0
0 259
文章 Hao Ma · 一月 10, 2021 11m read

在本文中,我想谈一谈规范优先的 REST API 开发方式。

传统的代码优先 REST API 开发是这样的:

  • 编写代码
  • 使其支持 REST
  • 形成文档(成为 REST API)

规范优先遵循同样的步骤,不过是反过来的。 我们先制定规范(同时兼做文档),然后根据它生成一个样板 REST 应用,最后编写一些业务逻辑。

这是有好处的,因为:

  • 对于想要使用你的 REST API 的外部或前端开发者,你总是有相关且有用的文档
  • 使用 OAS (Swagger) 创建的规范可以导入各种工具,从而进行编辑、客户端生成、API 管理、单元测试和自动化,或者许多其他任务的简化
  • 改进了 API 架构。 在代码优先的方式中,API 是逐个方法开发的,因此开发者很容易失去对整体 API 架构的跟踪,但在规范优先的方式中,开发者被强制从 API 使用者的角度与 API 进行交互,这通常有助于设计出更简洁的 API 架构
  • 更快的开发速度 - 由于所有样板代码都是自动生成的,你无需编写代码,只需开发业务逻辑。
  • 更快的反馈循环 - 使用者可以立即查看 API,并且只需修改规范即可轻松提供建议 让我们以规范优先的方式开发 API 吧!
0
0 357
文章 Hao Ma · 一月 10, 2021 17m read

RESTful 应用程序编程接口 (API) 设计和文档编制初学者指南。 通过示例,您将学习一些常见的 RESTful API 模式。

在阅读之前

您需要知道

  • 如何在 Ensemble 中创建 RESTful Web 服务
  • 如何在 Ensemble 中使用 RESTful Web 服务
  • 如何传递服务参数
  • 如何返回服务结果

什么是服务 API?

什么是应用程序编程接口? 是具体化的东西吗? 是单一编程单元吗? API 的作用是什么? 在我看来,API 是由程序代码以间接方式决定的。 但完全定义的 API 是由运行可执行程序的容器(由部署设置控制)提供的。 因此,我宁愿将 API 定义为服务的公共描述。 该描述可以是人类可读的,也可以仅机器可读, 或者两者均可。 API 用于与将要使用服务的人员共享有关服务的基本信息。 API 说明了服务的作用、使用环境、功能以及管理的数据结构等。

在过去的好时光,“编制程序文档”或多或少是一种“必要之恶”。 现代编程语言通过在程序源码中引入声明来强制编制文档。 虽然声明是“机器人”可读的文档,但通过使用工具(runoff、Java doc...),可以提取信息并将其格式化成人类可读的格式。 即使没有在源码中添加任何一行真正的文档,这些工具仍然能够生成少量文本。

现在有什么不同吗? 并没有。 服务 API 仍然是一个抽象的概念,表示正常使用一个功能性计算机软件所需的信息集合。 有一些语言可以将 API 定义形式化,例如 Web 服务描述语言 (WSDL)。 遗憾的是,这些语言的使用受限。 原因并不在于,例如,WSDL 的能力不足以表达 RESTful API,而是因为非技术上的不匹配。 (用 XML 表达 JSON 结构会是怎样的?) 最终,没有像适用于 SOAP Web 服务的 WSDL 那样的适用于 REST 的实际标准语言。 很可惜。 是不是?

没关系。 无论如何,我们首先需要了解 API 编制的内容。

API 由什么组成?

服务的核心属性是什么?

  • 服务位置。 服务的 URL 根路径。 例如 http://localhost:57774/csp/msa/person
  • 服务方法。 即服务的功能。 方法由 HTTP 标头中的动词(GET、POST、PUT…)与其他路径类型参数的组合定义。
  • 接受的方法参数。 参数及其类型的列表。 类型可以是路径,表示 URL 路径中包含的参数;查询,表示编码的 URL 查询;表单,表示表单数据;内容,表示 HTTP 消息正文。
  • 返回状态。 HTTP 响应标头中的状态字段。 每个服务方法可能有多个返回状态码。 数字取决于服务方法和异常处理的粒度。
  • 响应内容。 每个状态码的预期内容。 格式可能因状态码而异。 例如,成功完成某个请求后,预期获得一个 JSON 序列化对象。 如果服务器出错 (500),将发送纯文本说明。

示例 API

首先,让我们试着描述一下我们要实现的目标。 我们要构建一个非常简单的服务。 一个注册表服务。 它将管理相同类型的资源。 例如人员。

结构非常简单:姓名、出生日期和地点、母亲的娘家姓和生成的内部唯一注册表 ID。 地点的结构如下:国家/地区、城市。

我们要向注册表插入一条新记录(更新完整的注册表条目),更新条目的各个属性(更新属性),删除条目,按注册表 ID 获取单个条目,根据属性匹配查询注册表 ID 列表。

我们还需要一些服务功能:初始化注册表,填充一些记录以进行测试。

从外部看,我们希望 http://localhost:57774/csp/msa/person 为服务位置。

在服务位置通过 PUT 添加新条目。 将注册表记录作为内容发送。 预期返回带注册表 ID 的完整条目。

通过 POST 更新。 URL 包含要更新的条目的注册表 ID。 要更新的属性以表单数据的形式发送。

GET 用于检索数据。 如果 URL 路径以 ID 结尾,则返回由 ID 标识的注册表条目。 如果找不到 ID,但 URL 中有查询,则返回 ID 列表。 例如 http://localhost:57774/csp/msa/person/12A33 返回条目 12A33。 查询键值对是内部用于选择条目的属性匹配子句。 例如,http://localhost:57774/csp/msa/person?name=Hahn%20Istvan&dob=1961 返回出生于 1961 年且姓名为 Hahn Istvan 的人员名单。

DELETE 执行删除。

POSThttp://localhost:57774/csp/msa/person/_init 将初始化注册表。

POSThttp://localhost:57774/csp/msa/person/_populate/100 将加载 100 条测试条目。

API 文档

下面一节给出了一个如何对服务 API 编制文档的示例。 请记住,结构和内容均未标准化。 这只是一个示例。

我尽量让文档编制变得“工具不可知”。 市场上已有文档编制工具。 其中一些的表现已几乎达到应有的效果。 本节的目的是让您感受一下,使用文本编辑器来编制 API 文档有多复杂。

资源:            人员

一个用于管理人员类型资源的通用服务。 人员具有最少的一组属性。 基本上是人口统计特征和注册表 ID。

位置:    http://localhost:57774/csp/msa/person

方法:

根据资源的唯一 ID 获取单个资源。

动词:GET

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
  资源 ID
</td>

<td>
  要检索的资源的唯一 ID。
</td>
名称
1

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  找到记录。
</td>
<td>
  无
</td>

<td>
  不存在该资源 ID 的记录。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
204
401
403
500
501
503

 

方法:

根据非唯一查询获取匹配的资源 ID 列表。 该方法使用查询部分来构建查询字符串。 查询键/值对会转换为列名/值对。

动词:GET

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
  搜索条件。
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  查询
</td>

<td>
  日期
</td>

<td>
   
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  查询
</td>

<td>
  字符串
</td>

<td>
   
</td>
名称
name
motherMaidenName
dob
birthPlaceCounty
birthPlaceCity

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  找到记录。
</td>
<td>
  无
</td>

<td>
  无匹配记录。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
204
401
403
500
501
503

 

方法:

从注册表中删除条目。

动词:DELETE

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
  字符串
</td>

<td>
  唯一注册表 ID。
</td>
名称
1

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  记录已删除。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

方法:

向注册表添加或更新条目。

动词:PUT

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  内容
</td>

<td>
  JSON
</td>

<td>
  序列化为 JSON 格式的对象。
</td>
名称

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  注册表服务已新增或更新具有生成的资源 ID 的条目。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

方法:

更新注册表条目的单个属性。

动词:POST

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>

<td>
   
</td>
<td>
  路径
</td>

<td>
  字符串
</td>

<td>
  要更新的条目的资源 ID。
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
  属性的新值
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  日期
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
   
</td>
<td>
  表单
</td>

<td>
  字符串
</td>

<td>
   
</td>
名称
1
name
motherMaidenName
dob
birthPlaceCounty
birthPlaceCity

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  人员
</td>

<td>
  记录已更新。
</td>
<td>
  无
</td>

<td>
  不存在该资源 ID 的记录。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
204
401
403
500
501
503

 

方法:

初始化注册表。

动词:POST

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
   
</td>

<td>
   
</td>
名称
_init

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  无
</td>

<td>
  已初始化。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

方法:

填充测试数据。

动词:POST

参数:

<td>
  类型
</td>

<td>
  数据类型
</td>

<td>
  注释
</td>
<td>
  路径
</td>

<td>
   
</td>

<td>
   
</td>
<td>
  路径
</td>

<td>
  数值
</td>

<td>
  要填充的条目数。
</td>
名称
_populate
2

响应:

<td>
  返回类型
</td>

<td>
  注释
</td>
<td>
  无
</td>

<td>
  已初始化。
</td>
<td>
  无
</td>

<td>
  未经授权的访问。 该资源需要在标头中提供用户凭据。
</td>
<td>
  无
</td>

<td>
  被禁止。 用户无权访问该资源。
</td>
<td>
  错误
</td>

<td>
  内部服务器错误。
</td>
<td>
  错误
</td>

<td>
  请求的方法未实现。
</td>
<td>
  错误
</td>

<td>
  服务暂时不可用。
</td>
状态
200
401
403
500
501
503

 

数据结构:

人员

名称类型标记注释
ID注册表 IDR生成的注册表 ID。
Name字符串R该人员的母语形式的姓名。
DOB日期R出生日期。
BirthPlace出生地O出生地。
MotherMaidenName字符串O母亲的娘家姓。

出生地

名称类型标记注释
Country字符串O国家/地区代码。
City字符串R城市名称

错误

名称类型标记注释
Code字符串R错误代码
Text字符串O错误文本
InnerError错误O报告组件的子组件报告内部错误。

实现

以下部分给出了我们前面讨论的资源注册表的示例。 这同样只是一个示例。

为了让您更容易理解,我人为地将源码进行了分组。

n  属于 API 的所有内容都放到资源映射类中。

n  完整的 UrlMap XData 块分成单个 Route 条目。

n  每个条目都粘附到实际实现功能的静态方法。

所以,要恢复真实的类,需要进行一些(重新)设计。 请愉快地(重新)设计!

第一个服务方法是查询...



<

Route Url="/:service" Method="GET" Call="QueryRegistry"/>

classmethod QueryRegistry(service) as %Status {     

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.runQuery(..getQueryParameters($listbuild("name","dob","motherMaidenName","birthPlaceCountry","birthPlaceCity"))))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/:registryID" Method="GET" Call="GetEntry"/>

classmethod GetEntry(service,registryID) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.get(registryID))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/:registryID" Method="DELETE" Call="DeleteEntry"/>

classmethod DeleteEntry(service,registryID) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.delete(registryID))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/_init" Method="POST" Call="InitializeRegistry"/>

classmethod InitializeRegistry(service) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.init())

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/_populate/:numberOfRecords" Method="POST" Call="Populate"/>

classmethod Populate(service,numberOfRecords) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.populate(numberOfRecords))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service/:registryID" Method="POST" Call="UpdateAttribute"/>

classmethod UpdateAttribute(service,registryID) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.updateAttribute(registryID, ..getFormParameters($listbuild("name","dob","motherMaidenName","birthPlaceCountry","birthPlaceCity"))))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 



<

Route Url="/:service" Method="PUT" Call="AddOrUpdate"/>

classmethod AddOrUpdate(service) as %Status {

        try {

               set serviceInstance = ..getServiceInstance(service)

               do ..dumpResponse(serviceInstance.addOrUpdate(..getContentParameter()))

        }

        catch ex {

               do ..ReportHttpStatusCode(..getHTTPStatusCode(ex),ex.AsStatus())

        }

        quit $$$OK

}

 

现在是时候分享您的实用方法了。

classmethod getServiceInstance(serviceName) as Ens.BusinessService {

        set status = ##class(Ens.Director).CreateBusinessService(serviceName, .instance)

        throw:$$$ISERR(status) ##class(NoProduction).%New(status)

        quit instance

}

 

classmethod getHTTPStatusCode(ex) {

        quit $case(ex.%ClassName(1),

                              ##class(NoProduction).%ClassName(1)                  :503,

                              ##class(NotImplemented).%ClassName(1)        :501,

                                                                                                                 :500)

}

 

classmethod dumpResponse(responseObject) {

        if $isObject(responseObject) {

               if responseObject.%Extends(##class(%DynamicObject).%ClassName(1)) { write responseObject.%ToJSON() }

               elseif responseObject.%Extends(##class(%ZEN.proxyObject).%ClassName(1)) {

                       do ##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(responseObject)

               }

               elseif responseObject.%Extends(##class(%XML.Adaptor).%ClassName(1)) {

                       do responseObject.XMLExportToString(.ret)

                       write ret

               }

               else { throw ##class(Serialization).%New() }

        }

        else {

               write responseObject

        }      

}

 

classmethod getQueryParameters(parameterList) as %DynamicObject {

        set parameterObject = {}

        for i=1:1:$listlength(parameterList) {

               set parameterName=$listget(parameterList,i)

               set $property(parameterObject, parameterName) = %request.Get(parameterName)

        }

        quit parameterObject

}

 

classmethod getFormParameters(parameterList,queryObject) as %DynamicObject {

        if $data(queryObject) { set parameterObject = queryObject }

        else { set parameterObject = {} }

        for i=1:1:$listlength(parameterList) {

               set parameterName=$listget(parameterList,i)

               set $property(parameterObject, parameterName) = %request.Get(parameterName)

        }

        quit parameterObject

}

 

classmethod getContentParameter() as %DynamicObject {

        quit {}.%FromJSON(%request.Content)

}

 

到此为止。 我们完成了一个 RESTful Web 服务 API 的设计(?)、实现和文档编制。

敬请关注,我很快会回来进一步解读 Ensemble RESTful Web 服务。 下一个主题是“使用 RESTful Web 服务创建 Ensemble 微服务”。

0
0 184
文章 Jeff Liu · 一月 8, 2021 3m read

关注开发者社区全栈竞赛的朋友会知道,我提交了一个名为qewd-conduit的参赛作品。我想总结一下,为什么我认为您应该花点时间来看看这个作品。

qewd-conduit 使用基于 Node.js QEWD 框架和 IRIS,可以为 RealWorld Conduit 应用程序实现后端 REST API

https://github.com/gothinkster/realworld

这个方案很酷,它提供了一个平台,让很多人可以为特定应用程序的后端和前端,实现不同的技术解决方案。而qewd-conduit只是符合相同REST API后端规范的众多解决方案之一。同样,要实现完全相同的UI/UX,您可以尝试使用众多不同前端客户端中的任何一个,并通过REST与包括QEWD-Conduit在内的任何Conduit后端集成。

这样就可以用各种不同的框架和技术,去执行完全相同的任务,从而对不同的框架和技术进行比较和对比。

RealWorld应用程序很好地平衡了相关性和非琐碎性(不仅仅是ToDo应用程序!),并且也不是太复杂,无论是在UI/UX还是在后端API方面。因此便于展示和说明如何使用特定技术来实现RealWorld的指定功能。

0
0 152
文章 Jeff Liu · 一月 7, 2021 2m read

最近,我需要从持久类和序列类生成一个 Swagger 规范,所以我发布了我的代码(它并不完整 - 你仍然需要处理应用程序的细节,但这是一个开始)。 代码在这里

假设你有下面的类:

 

你可以通过以下代码自动生成此 Swagger 定义:

 REST.Test.Person:
   type: "object"
   properties:
     Age:
       type: "integer"
     DOB:
       type: "string"
     FavoriteColors:
       type: "array"
       items:
         type: "string"
     FavoriteNumbers:
       type: "object"
     Home:
       $ref: "#/definitions/REST.Test.Address"
     Name:
       type: "string"
     Office:
       $ref: "#/definitions/REST.Test.Address"
     SSN:
       type: "string"
     Spouse:
       $ref: "#/definitions/REST.Test.Person"
 REST.Test.Address:
   type: "object"
   properties:
     City:
       type: "string"
     State:
       type: "string"
     Street:
       type: "string"
     Zip:
       type: "string"

主方法:Utils.YAML:GenerateClasses

测试运行:do ##class(Utils.YAML).Test()

0
0 269