从零开始开发新的业务系统——做系统
我们已经学习不少关于GraphQL的知识了。在之前的例子中,使用的都是存储在内存中的数据,但在现实开发中,你需要使用的是GraphQL 模式后的真实数据源。
这也正是本节课所要完成的目标。目前国内医疗信息化厂商多使用 Oracle 数据库,医院内部的信息系统集成多采用点对点的接口模式,多采用 XML 的 SOAP 服务。目前已有一些大型三甲基于集成平台/院内信息平台实现了系统的互联互通,协议仍然多以XML 为主,由于使用了国外的数据引擎,故而部分是支持国家标准诸如 HL7 V2.X V3 CDA。对于实现了市级、区级的人口健康信息平台的城市,医疗机构之间仍多以前置机方式实现数据交互。下面我们以典型的院内HIS表结构(军卫一号)和某达的前置机表结构来给大家示范 GraphQL 如何在系统互联互通 接口设计和开发中应用来降低接口重复开发的成本。
以军卫一号 HIS 表结构为例
我们以网络上公开可访问的 《文档编号: 天健—0000—0601—DSM 天健 医院信息系统 数据结构手册 版本1.0.15(第15稿)》为例。
我们选取病人主索引、检查病人主索引和检查主记录、检查报告四张表
9.1 病人主索引 PAT_MASTER_INDEX
字段中文名称 字段名 类型 长度 说明
病人标识号 PATIENT_ID C 10 病人唯一标识号,可以由用户赋予具体的含义,如:病案号,门诊号等
住院号 INP_NO C 10 可选的病人标识,可为空
姓名 NAME C 20 病人姓名
姓名拼音 NAME_PHONETIC C 16 病人姓名拼音,大写,字间用一个空格分隔,超长截断
性别 SEX C 4 男、女、未知,本系统定义,见1.1性别字典
出生日期 DATE_OF_BIRTH D
出生地 BIRTH_PLACE C 6 指定省市县,使用代码,见2.2行政区字典
国籍 CITIZENSHIP C 2 使用国家代码,见2.1国家及地区字典
民族 NATION C 10 民族规范名称,见1.3民族字典
身份证号 ID_NO C 18
身份 IDENTITY C 10 由身份登记子系统生成,住院登记子系统在办理入院时更新。使用规范名称,由用户定义,见1.6身份字典
费别 CHARGE_TYPE C 8 由身份登记子系统生成,住院登记子系统在办理入院时更新。使用规范名称,由用户定义,见1.9费别字典
合同单位 UNIT_IN_CONTRACT C 11 如果病人所在单位为本医院的合同单位或体系单位,则使用代码,否则为空。由身份登记子系统生成,住院登记子系统在办理入院时更新。代码由用户定义,见2.4合同单位记录
通信地址 MAILING_ADDRESS C 40 指永久通信地址
邮政编码 ZIP_CODE C 6 对应通信地址的邮政编码
家庭电话号码 PHONE_NUMBER_HOME C 16
单位电话号码 PHONE_NUMBER_BUSINESS C 16
联系人姓名 NEXT_OF_KIN C 8 病人的亲属姓名
与联系人关系 RELATIONSHIP C 2 夫妻、父子等,使用代码,见1.19社会关系字典
联系人地址 NEXT_OF_KIN_ADDR C 40
联系人邮政编码 NEXT_OF_KIN_ZIP_CODE C 6
联系人电话号码 NEXT_OF_KIN_PHONE C 16
上次就诊日期 LAST_VISIT_DATE D 7 由挂号与预约子系统根据就诊记录填写
重要人物标志 VIP_INDICATOR N 1 1-VIP 0-非VIP
建卡日期 CREATE_DATE D 7
操作员 OPERATOR C 8 最后修改本记录的操作员姓名
医疗体系病人标志 SERVICE_AGENCY C 40
单位邮编 business_zip_code C 6
照片 photo Long raw
入院来源 PATIENT_CLASS C 使用代码,门诊、急诊、转入等,见1.11入院方式字典
学历 degree C 10 见学历字典
种族 race C 10 见种族字典
宗教 religion C 16 见宗教字典
母语 Mother_language C 16 见语言字典
第二外语 Foreign_language C 16 所能接受的服务语言,见语言字典
证件类型 ID_type C 10 见证件类型字典
会员号 Vip_no C 18
英文名字 e_name var 100 住院登记、身份登记、主索引录入时增加英文名字。
注释:此表描述所有在院注册的病人的基本信息,被整个医院信息系统所共享。由身份登记子系统录入。 此表信息需长期在线保存,如果使用挂号子系统,则此表的数据增长量与每日的初诊病人数相一致。如果医院每日门诊量为1000,其中1/4为初诊病人,则每日数据增长量约为250条,每年约为80,000条。 主键:病人标识号。
10.5 就诊记录 CLINIC_MASTER
字段中文名称 字段名 类型 长度 说明
就诊日期 VISIT_DATE D 非空
就诊序号 VISIT_NO N 5 非空,每天从1开始递增,为病人每次挂号分配一个序号,该序号与就诊日期一起,构成一次就诊的唯一标识
号别 CLINIC_LABEL C 16 对应门诊号表主记录定义的号别
就诊时间描述 VISIT_TIME_DESC C 8 同号表门诊时间描述
号码 SERIAL_NO N 3 一个号别下的序号
病人标识 号 PATIENT_ID C 10 对已建立主索引的病人使用,对无主索引的病人为空
姓名 NAME C 20 病人姓名
姓名拼音 NAME_PHONETIC C 16 病人姓名拼音,大写,字间用一个空格分隔,超长截断
性别 SEX C 4 使用规范描述,见1.1性别字典
年龄 AGE N 3
身份 IDENTITY C 10 使用规范名称,用户定义,见1.6身份字典
费别 CHARGE_TYPE C 8 使用规范名称,用户定义,见1.9费别字典
医保类别 INSURANCE_TYPE C 16 如果此病人为医保病人,则记录反映本次住院支付方案的医保类别
医疗保险号 INSURANCE_NO C 18 如果此病人为医保病人,则记录其保险号
合同单位 UNIT_IN_CONTRACT C 11 也称体系单位,使用代码,用户定义,见2.4合同单位记录
号类 CLINIC_TYPE C 8 标识该门诊的挂号费等级,如:普通、专家等,见3.5门诊号类别字典
初诊标志 FIRST_VISIT_INDICATOR N 1 1-初诊 0-复诊
就诊科室 VISIT_DEPT C 8 科室代码,用户定义,见2.6科室字典
就诊专科 VISIT_SPECIAL_CLINIC C 16 指就诊科室下所设的某一专科,可空
医生 DOCTOR C 8 在就诊专家门诊时,为专家姓名,可空
提供病案标志 MR_PROVIDE_INDICATOR N 1 1--需提供 0--不提供
挂号状态 REGISTRATION_STATUS N 1 反映从预约到就诊的状态变化。0-预约 1-已确认(已取号) 2-就诊
挂号日期 REGISTERING_DATE D 7 发生预约或挂号的日期
症状 SYMPTOM C 40
挂号费 REGIST_FEE N 5,2
诊疗费 CLINIC_FEE N 5,2
其它费 OTHER_FEE N 5,2
实收费用 CLINIC_CHARGE N 5,2
挂号员 OPERATOR C 8
退号日期 RETURNED_DATE D 发生退号时使用
退号挂号员 RETURNED_OPERATOR C 8 发生退号时使用
挂号模式 MODE_CODE C 1
卡名 CARD_NAME C 16
卡号 CARD_NO C 20
结帐时间 ACCT_DATE_TIME D
结帐号码 ACCT_NO C 6
支付方式 PAY_WAY C 8
病案传送否 MR_PROVIDED_INDICATOR N 1
发票号码 INVOICE_NO C 20
门诊号 clinic_no V2 13
注释:此表反映病人一次就诊或挂号的基本信息,挂号时产生,由病人门诊的后续环节如:收费、取药等使用。病人完成整个门诊流程,待门诊业务统计完成后,即可删除挂号记录。
允许提前挂号,即提前拿号和交费(不同于预约)。
主键:就诊日期、就诊序号。
14.1 检查病人主索引 EXAM_PAT_MI
字段中文名称 字段名 类型 长度 说明
检查号类别 LOCAL_ID_CLASS C 1 每种检查,允许使用各自本地的标识号,如:超声号、X光号,本字段用于区分不同类型的本地标识号,由用户定义
检查标识号 PATIENT_LOCAL_ID C 10 如:超声号、X光号,与检查标识号类别一起构成检查主索引的唯一标识
病人标识号 PATIENT_ID C 10 不要求申请的病人必须具备主索引,但对具有主索引的病人使用此项,以便与其他系统的医疗数据关联
姓名 NAME C 20 病人姓名,非空
姓名拼音 NAME_PHONETIC C 16 病人姓名拼音,大写,字间用一个空格分隔,超长截断
性别 SEX C 4 病人性别,男、女、未知等,1.1性别字典
出生日期 DATE_OF_BIRTH D 病人出生日期
出生地 BIRTH_PLACE C 6 或称为籍贯,使用代码,见2.2行政区字典
身份 IDENTITY C 10 使用规范名称,如:师以上、团以下、战士、免费职工、家属、地方其他等,见1.6身份字典
费别 CHARGE_TYPE C 8 使用规范名称,如:免费、包干、军半费、军全费、公费、自费、其他等,见1.9费别字典
通信地址 MAILING_ADDRESS C 40
邮政编码 ZIP_CODE C 6 数字型字符
联系电话 PHONE_NUMBER C 16
注释:此表用于反映病人各种检查本地标识信息。本表的信息与病人主索引有一定的重复,但通过本表的设置,使得检查系统与其他系统相对独立,可以不依赖于病人主索引信息,这有利于满足辅诊科室社会化服务的模型。从电子病历角度来看,通过本表,可以查得一个病人各种相关标识号。如果病人具备主索引信息,在提取病人检查信息时,可以不通过本表,直接与检查记录关联。
不要求所有的检查都使用本表,特别是不建立病人本地档案的系统,可以不使用本表,而直接使用病人主索引(确保所有的病人有主索引)。
在病人建立各种检查档案时(预约时仅建立预约记录,不分配标识号,故不创建该记录),由特检分系统建立该记录。
14.3 检查主记录 EXAM_MASTER
字段中文名称 字段名 类型 长度 说明
申请序号 EXAM_NO C 10 每个检查申请的唯一标识,在整个系统范围内唯一。序号可采用集中分配方式,序号的组成可由申请日期加上一个顺序号或使用累计序号
检查号类别 LOCAL_ID_CLASS C 1 每种检查,允许使用各自本地的标识号,如:超声号、X光号,本字段用于区分不同类型的本地标识号,由用户定义
检查标识号 PATIENT_LOCAL_ID C 10 如:超声号、X光号,与检查标识号类别一起构成检查主索引的唯一标识
病人标识号 PATIENT_ID C 10 不要求申请的病人必须具备主索引,但对具有主索引的病人使用此项
病人本次住院标识 VISIT_ID N 2 对住院病人使用此项,门诊或外来病人可为空
姓名 NAME C 20 病人姓名,非空
性别 SEX C 4 病人性别,本系统定义,见1.1性别字典
出生日期 DATE_OF_BIRTH D 病人出生日期
检查类别 EXAM_CLASS C 6 区分各类检查,使用规范名称,用户定义,见3.3检查类别字典
检查子类 EXAM_SUB_CLASS C 8 区分一种检查类下的各子类,如超声类下的腹部、心脏、妇产等子类,使用规范名称,用户定义,见3.4检查子类字典
标本收到日期及时间 SPM_RECVED_DATE D 此项由执行科室填入
临床症状 CLIN_SYMP C 400 正文描述
体征 PHYS_SIGN C 400 正文描述
相关化验结果 RELEVANT_LAB_TEST C 200 正文描述
其他诊断 RELEVANT_DIAG C 400 依靠其他检查手段得到的有关诊断
临床诊断 CLIN_DIAG C 80 主要临床诊断,正文描述
检查方式 EXAM_MODE C 1 描述检查的场所,本系统定义,使用代码,A=病房 B=检查科室
检查分组 EXAM_GROUP C 16 用于标识预约排队队列。每个队列称为一个检查组。它可能是一台仪器对应多个检查项目构成的排队队列,也可能是多台仪器对应一个检查项目构成的一个排队队列
使用仪器 DEVICE C 20 指检查所使用的仪器名称和型号
执行科室 PERFORMED_BY C 8 使用代码,用户定义,见2.6科室字典
病人来源 PATIENT_SOURCE C 1 门诊、病房、外来,使用代码,见1.10病人来源字典
外来医疗单位名称 FACILITY C 20 当病人为外来时,使用此字段表示病人所在医院或医疗机构名称。当病人来自本院时,留空
申请日期及时间 REQ_DATE_TIME D 提出此申请的日期及时间
申请科室 REQ_DEPT C 8 使用代码,用户定义,见2.6科室字典
申请医生 REQ_PHYSICIAN C 8 医生姓名
申请备注 REQ_MEMO C 60 由申请者输入与申请有关的需另外说明的问题
预约日期及时间 SCHEDULED_DATE_TIME D 此字段由执行科室根据预约安排填写
注意事项 NOTICE C 400 此字段由执行科室填写,填入与申请项目有关需注意的事项。该项与预约日期及时间一起构成对检查申请的答复。
检查日期及时间 EXAM_DATE_TIME D 实际执行检查的日期及时间
报告日期及时间 REPORT_DATE_TIME D 医生确认报告的日期及时间
操作者 TECHNICIAN C 8 操作的技术人员姓名
报告者 REPORTER C 8 确认报告的医生姓名
检查结果状态 RESULT_STATUS C 1 反映申请的执行情况,如:收到申请、已执行、初步报告、确认报告等,初始时,由申请方填入,以后根据检查的执行情况,由执行者修改,使用代码,本系统定义,见3.7检查结果状态字典
费用 COSTS N 8,2 按标准价格计算得到的本检查的费用,由执行科室划价后填入
应收费用 CHARGES N 8,2 考虑费别因素后,计算得到的本检查的费用,由执行科室划价后填入
计价标志 CHARGE_INDICATOR N 22,0 0-未计价 1-已计价,由收费系统或者检查系统在计费后填入
病人费用类别 CHARGE_TYPE C 8
身份 IDENTITY C 10
报告状态 RPTSTATUS C 50 Pacs
打印状态 PRINT_STATUS C 50 Pacs
检查子科室 EXAM_SUBDEPT C 10
确认医师 CONFIRM_DOCT C 8
学习号 STUDY_UID C 128 Pacs
注释:此表记录病人各种检查的发生及执行情况,是电子病历中检查信息的主索引。当执行检查时,将检查预约记录中的信息转入本表中,或者在执行时录入。当检查完成后,将此表中各种执行信息填入。
收费系统可根据本表中结果状态,选择不同的计价时间点,如预约时或报告确认后等。计价后,将计价标志置位。
主键:申请序号。
索引:病人 ID;
检查号类别、检查标识号;
14.5 检查报告 EXAM_REPORT
字段中文名称 字段名 类型 长度 说明
申请序号 EXAM_NO C 10 在检查申请记录中分配的唯一标识
检查参数 EXAM_PARA C 1000 检查过程中记录的有关内容
检查所见 DESCRIPTION C 2000
印象 IMPRESSION C 2000
建议 RECOMMENDATION C 2000
是否阳性 IS_ABNORMAL C 1 1-阳性,即检查可能有病变,其他为阴性
使用仪器 DEVICE C 20
报告中图象编号 USE_IMAGE C 15 如果报告涉及到图象,则此处给出图象的引用索引,格式:XXX(报告中的第一幅图对应该检查中第XXX幅图象,依次类推),YYY…
备注 MEMO C 2000 可以放各种检查的互相参考内容
9.28 医疗卡说明MEDICAL_CARD_MEMO(新增)
字段中文名称 字段名 类型 长度 说明
病人标识号 PATIENT_ID C 10 病人唯一标识号,可以由用户赋予具体的含义,如:病案号,门诊号等
卡号 CARD_NO C 18
卡类型 CARD_TYPE C 10
有效期限 EXPIRATION_DATE D 系统默认医疗卡年度有效,医院每年进行年审。
状态 ACCOUNT_STATUS N 1 0-可用 1-挂起 2-注销
364.3 卫生信息数据元值域代码 第3部分:人口学及社会经济学特征 身份证件类别代码表 CV02.01.101 01 居民身份证 02 居民户口簿 03 护照 04 军官证 05 驾驶证 06 港澳居民来往内地通行证 07 台湾居民来往内地通行证 99 其他法定有效证件
(7) 卡类型的编码如下 0 = 社保卡 1 = 新农合卡 2 = 医疗机构系统内部号(保证唯一),若就诊病人未持卡,卡类型可填2,卡号填写医疗机构系统内部号 3 = 自费就诊卡 4 = 医保卡 5 = 健康卡 9 = 其它
我们将选择 ORACLE 作为数据源,并在课程的后续演示如何集成其他数据源。
现在就开始吧!
准备工作
首先你需要一个 ORACLE 数据库。 安装 此处省略五百字。
然后,复制GraphQL Sandbox项目到本地:
git clone https://github.com/kadirahq/graphql-blog-schema.git
cd graphql-blog-schema
接着,切换到server-side-schema
分支。
git checkout server-side-schema
最后,安装依赖项并启动它:
npm install
npm start
我们就可以在3000端口访问shandbox项目了,http:// localhost:3000
这版的sandbox与之前的稍有不同。在前几节课的例子中,我们都是在客户端里定义schema,但这次,我们要在服务器端来定义它。
为此,我们将使用express-graphql项目。
模式文件
在本项目中,schema文件位于server / schema.js
中,并且已经定义好了一个authors
的根查询字段和一个名为createAuthor
的mutation。
如果你学习了之前的课程的话,相信对这些东东都不会陌生吧:)
Async / promises
回想之前的例子,我们只是在resolve函数中简单地返回一个值。由于数据都在内存中,所以并不会导致什么问题。但当我们访问一个真实的数据源时,立即返回结果几乎是不可能的,因此必须有异步的处理。
ES2015 promise能帮助我们解决这个问题。我们可以在resolve函数里返回一个promise, 然后GraphQL就能快速有效的处理它们。
访问MongoDB
我们需要一个支持promise的MongoDB驱动程序来访问MongoDb,这本项目中,将使用promised-mongo,它已经安装在我们的repo里了
实现mutation
首先,需要创建一个MongoDB的连接,我们在schema.js
的顶部添加下面的代码:
const mongo = require('promised-mongo');
// You can use any MONGO_URL here, whether it's locally or on cloud.
const db = mongo('mongodb://localhost/mydb');
const authorsCollection = db.collection('authors');
然后实现createAuthor
的处理逻辑。so,我们使用promised-mongo
这个包来提供promise的API。
mutation与mongoDB相关的代码如下:
const Mutation = new GraphQLObjectType({
name: "Mutations",
fields: {
createAuthor: {
type: Author,
args: {
_id: {type: new GraphQLNonNull(GraphQLString)},
name: {type: new GraphQLNonNull(GraphQLString)},
twitterHandle: {type: GraphQLString}
},
resolve: function(rootValue, args) {
let author = Object.assign({}, args);
return authorsCollection.insert(author)
.then(_ => author);
}
}
}
});
现在你可以按照下面的方法在GraphQL Sandbox里调用mutation了
mutation _ {
createAuthor(
_id: "john",
name: "John Carter"
) {
name
}
}
代码执行后,你会得到以下结果:
{
"data": {
"createAuthor": {
"name": "John Carter"
}
}
}
任务时间!
试试再次调用上面的mutation。 会得到什么结果咧?
- It was successful as usual.
- Got an empty response.
- Got an error stating that the insert had been forbidden.
- Got an error citing a duplicate key error.
实现根查询字段
通过上一节课,我们完成了添加新作者的方法,现在就让我们来实现authors
查询字段。
这将是根查询类与MongoDB的集成哦。
const Query = new GraphQLObjectType({
name: "RootQuery",
fields: {
authors: {
type: new GraphQLList(Author),
resolve: function() {
return authorsCollection.find().toArray();
}
}
}
});
然后,你就可以在GraphQL SandBox中检索作者了:
{
authors {
name
}
}
ok的话,你能够从Mongo数据库中获取到所有作者的名字。
运行似乎很顺利嘛,可是却有一个小问题哦。看看你是否发现它了:
- It can’t do field filtering in MongoDB level.
- It can't limit the result.
- It can't sort the result.
- There is no such issue.
即使没有排序和限制功能,我们仍然可以通过传递一些参数来简单的实现这些效果。 但是如何过滤出那些在查询中提及的字段呢?
这正是本节课我们要完成的内容。
字段过滤
虽然没有直接的方法从resolve函数检索字段。 但仍有解决办法。看看修改后的resolve函数吧:
const Query = new GraphQLObjectType({
name: "Queries",
fields: {
authors: {
type: new GraphQLList(Author),
resolve: function(rootValue, args, info) {
let fields = {};
let fieldASTs = info.fieldASTs;
fieldASTs[0].selectionSet.selections.map(function(selection) {
fields[selection.name.value] = 1;
});
return authorsCollection.find({}, fields).toArray();
}
}
}
});
我们使用了传给resolve函数的第三个参数:info。它有该查询解析后的AST数据。通过它,我们就可以检索字段啦,如上面的代码所示。
不止于此
即使仅使用promised-mongo
,我们也可以很容易地将Mongo数据库与GraphQL模式集成。当然,这只是方法之一,还有好几种方式能够将现有数据源集成到GraphQL中,让我来告诉你它们中的一些吧:
- graffiti - 使用Mongoose模式与GraphQL集成
- graphql-sequelize - 使用Sequelize ORM模式与GraphQL集成
- graphql-bookshelf - 使用BookshelfJS模型集成。
这仅仅是个开始。 将来会有有越来越多的方法实现GraphQL与不同类型数据源的集成。