从零开始开发新的业务系统——做系统

我们已经学习不少关于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中,让我来告诉你它们中的一些吧:

这仅仅是个开始。 将来会有有越来越多的方法实现GraphQL与不同类型数据源的集成。

results matching ""

    No results matching ""