巴西柔术_案例研究:巴西国家医疗体系

巴西柔术

巴西国家医疗保健系统被称为有史以来最大的企业Java应用程序,具有超过200万行代码和350个类的域模型。 该应用程序可以在一个全国范围的医疗保健系统中模拟所有领域概念,并且带来了一定程度的自动化,为公共医疗保健系统以及巴西人民创造了巨大的价值。 本案例研究是同类案例中唯一的一种,它详细研究了体系结构,有趣的解决方案,经验教训以及项目的未来发展方向。

问题域

巴西是世界上拥有完全免费的公共医疗保健系统的少数国家之一。 像任何规模的公共服务一样,存在许多运营问题。 该系统的某些部分主要是基于纸张的,在政府和本地医疗保健提供者之间的现有IT系统之间几乎没有集成。 例如:

  1. 没有安排系统可以允许医疗保健提供者彼此预约患者。 例如,需要拜访心脏专科医师的患者将需要自行寻找一名可用的专科医师,通常是在多个专科医师排长队等待并预约重复的约会,直到找到足够早的开放时间。
  2. 缺乏有关患者历史的信息,例如任何护理点的手术历史或医疗状况。
  3. 同一位患者经常在多个单独的数据库中记录多次。
  4. 由于重要的统计数据(例如出生,死亡,医疗操作和疾病诊断)存储在未集成的纸质系统中,因此政府无法进行任何医疗资源容量规划或应对短缺情况。
  5. 由于建模健康立法和政策的IT系统未与即时医疗系统集成,因此无法检测和防止滥用医疗系统。
  6. 许多当地医生根本没有信息系统,由于缺乏安排,患者需要整天等待。

考虑到这些问题,调试了名为Siga Saude的医疗保健自动化系统。 该系统旨在处理公共卫生信息系统中可能想到的所有方面,包括医生和设备的日程安排和库存管理,账单,疾病跟踪,报告/审计,法规遵从性和安全访问控制。

该系统首先在圣保罗市(巴西最大的城市,也是世界第四大人口超过2000万的城市)中使所有医疗保健部门实现自动化。 如今,该应用程序已在圣保罗和其他20个城市中投入生产,并且正在努力将该系统扩展到巴西的其他城市,以及也有兴趣实现其国家医疗保健系统自动化的其他国家(例如,葡萄牙的葡萄牙语国家)安哥拉和莫桑比克。

解决方案概述

该应用程序基于EJB 2.1 + Struts,并利用已定义的分层架构,该架构使用已建立的EJB设计模式,包括数据传输对象,会话外观,服务定位器和业务委托。 开发是在Eclipse中完成的,测试和部署是在JBoss应用服务器上完成的。 在某些区域使用了规则引擎(Drools),并且该应用程序当前以非集群方式部署在运行JBoss 3.2.7的Linux上具有4 gigs RAM的Dual Xeon 3.1服务器上。

从概念到需求和开发,包括医生和卫生专家在内的150多人参与了该系统的功能定义。 开发团队将该系统细分为以下模块:

上面模块中的代码行包括服务层中的行和特定于那些模块的域模型。 此外,在服务层的模块之间共享10万行代码,而在域模型中共享570K行代码,尽管代码行的数量可能令人望而生畏,但实际上该行的58%数量的代码由生成的代码组成,这将在下面的注释使用中深入讨论。

上面的每个业务模型都包含JSP页面,Struts操作和调用服务层的业务代表。 服务层由具有生成的会话外观的EJB 2.1会话bean组成。 域模型由带有用于生成实体bean的注释的pojo组成。 下一次向下钻取将详细介绍从url到sql的调度系统。 此模块中的总体分层与其余模块一致,并且应充分理解系统的构建方式。

深入研究:安排专家用例的约会

从业务和技术角度来看,系统中最重要的用例之一是小型本地医生办公室的接待员能够安排专科医生预约患者的能力。 用例是在Scheduling模块中实现的,但涉及到系统中的许多其他模块。调度约会用例如下所示:


首先,接待员将在时间表中寻找空闲时间。 他们可以发布针对特定医生,专业,某种医疗设备,特定医疗程序等的查询(单击此处以查看输入屏幕 ),并在下面的序列图中查看“ getSlots”。 当他们找到一个空闲插槽(序列图上的chooseSlot())时,他们将使用国民健康卡模块来定位患者的档案,方法是扫描患者健康卡上的条形码,或者通过姓名,出生日期进行查找或其他任何联系信息(如果他们忘记了该卡)(请参阅searchPatient()。然后,他们将在同一屏幕上输入所需的程序类型(请参见第二个输入屏幕 ,请参见序列图上的saveAppointment())。诊所中没有可用的程序,因为没有免费的诊疗位,或者因为诊所没有提供那种服务,接待员或医生可以在其他诊所寻找免费的诊疗位。

此序列图显示了如何在系统中执行典型用例。 首先,Struts Action类(ScheduleAction)处理用户请求并处理表示逻辑。 当需要检索数据或需要调用业务规则时,Action类将调用实现业务委托设计模式(ScheduleCF)的POJO对象中的方法。 此类是从SessionFaçade类(ScheduleSF)自动生成的,并使Web层独立于用于服务层的技术。 实现会话外观设计模式(ScheduleSF)的会话Bean将业务规则的执行委托给其他类。 例如,类AppointmentService实现方法lockSlot()和searchForSlots()。 当用例需要保留数据时,将在EntityBean上调用setAppointmentVO()方法。 查询不是使用实体Bean机制执行的,而是使用称为Searcher的优化查询组件执行的。

SessionFaçade对象负责为应用程序的其余部分提供事务服务,数据库连接和安全服务。 服务对象是POJO,因此它们不能直接从Web层访问实体Bean,因此需要会话Bean层。

锁定插槽后,时间戳记和锁定ID将存储在插槽数据库表中。 这两个数据都存储在SELECT FOR UPDATE命令中,以避免出现竞争情况。 时间戳用于确定锁是否过期。 锁ID用于避免对象尝试保存过期锁的情况。 当请求锁定的对象决定保存约会时,它将接收到的锁定ID与表中存储的当前锁定ID进行比较。 如果它们不同,则无法保存新信息。 这样,如果锁定在请求锁定和保存约会之间过期,则应用程序可以检测到此情况并警告用户。 该用例实现了逻辑锁。 数据库锁和EJB锁都不长时间保存,因此可以更好地实现业务规则。

深化:使用注释生成代码

项目开始时,使用XDoclet 1生成部署描述符,值对象,查询,会话外观,Struts表单和操作,数据验证以及本地和远程EJB接口。 代码生成是该项目中的关键策略,因为项目时间较短,因此需要能够为开发人员提高生产率并减少错误数量的技术。 因此,XDoclet原始模板已更改为生成“专家代码”。

XDoclet方法在项目的前10个月效果很好,并帮助开发人员实现了所需的生产力。 一段时间后,随着代码的增加,生成时间也增加到了生成时间严重影响生产率的地步。 然后,团队决定使用Java 5注释转向另一种代码生成策略。

使用注释策略可以解决XDoclet的一些缺点:

  • 允许增量生成文件,例如部署描述符:使用XDoclet,当类更改时,它将再次读取所有类以生成新的部署描述符。 使用注释,可以仅生成部署描述符中受类更改影响的部分。 由于大多数情况下,开发人员只更改少数几个类,因此可以节省大量时间。
  • XDoclet 1要求在生成时访问子组件源代码,从而增加耦合和构建复杂性。 所有开发人员都需要访问整个源代码,而不仅仅是预期的编译后的jar。 使用注释,可以避免这种情况。
  • 注释可以在运行时进行处理,因此有时使用此功能代替生成代码会有所回报。

该团队开发了用于EJB 3.0注释和其他自定义注释的处理器,这些处理器将生成与XDoclet生成的代码相似的代码。 尽管使用了EJB 3批注,但是生成的代码符合EJB 2.1。

注释处理器所使用的模板,其中速度模板。 从XDoclet转向编写自己的模板时,最大的挑战是构建一套与XDoclet 1一样全面的模板和帮助程序类。

使用注释,生成时间大大减少了。 使用XDoclet,如果更改了一个类,则需要1分50秒才能从400个类中生成代码。 使用APT,相同的过程耗时10秒。

深入研究:规则引擎如何简化业务逻辑

系统的某些部分必须处理由于政府法规而随时间变化的业务规则。 例如,如果诊所要提供X射线服务,则必须遵守多个规则,例如拥有X射线机,有放射线师等。由于这些规则经常更改,因此最好将其保留在代码之外,因此可以在不更改代码的情况下进行更改。

在SIGA-Saúde中,使用Drools规则引擎解决了该问题,该引擎实现了JSR-94-Java Rule Engine API。 规则处理由名为Decision的SIGA-Saúde组件完成。 该组件与规则集和工作内存配合使用。 规则集与一组消息一起使用,其中每个规则都有一个消息,该消息由实现br.com.vidatis.common.decision.message.RuleMessage接口的类描述。 当满足一个规则或规则的子集时,它们的相关消息将从队列中删除。 这样,可以跟踪规则处理。 如果最后队列中没有消息,则满足所有规则。 一条规则可以触发几种不同的动作,包括一组新的规则。 规则在XML文件中描述,并由Drools引擎处理。

XML文件的示例:

<rule name="if_clinic_code_then_checksum_digit_valid">
<parameter identifier="clinic">
<java:class>br.atech.smssp.domain.clinic.vo.ClinicVO</java:class>

</parameter>
<java:condition>clinic.getCode() != null</java:condition>
<java:condition>!clinic.getCode().equals("")</java:condition>
<java:consequence>
//clinic code should be mandatory and valid
if(br.com.vidatis.common.decision.rule.CodeRule.isValidCone(clinic.getCode())){
ruleMessage.markRuleAsValid("if_clinic_code_then_checksum_digit_valid");
}
</java:consequence>

</rule>

<rule name="if_maintainer_code_then_checksum_digit_valid">
<parameter identifier="clinic">
<java:class>br.atech.smssp.domain.clinic.vo.ClinicVO</java:class>
</parameter>
<java:condition>clinic.getMaintainerCode() != null</java:condition>

<java:condition>!clinic.getMaintainerCode().equals("")</java:condition>
<java:consequence>
//Maintainer code is optional, but should be valid if it is informed.
if(br.com.vidatis.common.decision.rule.CodeRule.isValidCode(clinic.getMaintainerCode())){
ruleMessage.markRuleAsValid("if_maintainer_code_then_checksum_digit_valid");
}
</java:consequence>
</rule>

<rule name="if_maintainer_code_then_checksum_digit_valid_OPTIONAL_EMPTY">
<parameter identifier="clinic">

<java:class>br.atech.smssp.domain.clinic.vo.ClinicVO</java:class>
</parameter>
<java:condition>clinic.getMaintainerCode() != null</java:condition>
<java:condition>clinic.getMaintainerCode().equals("")</java:condition>
<java:consequence>

//Maintainer code is optional and can be empty
ruleMessage.markRuleAsValid("if_maintainer_code_then_checksum_digit_valid");
</java:consequence>
</rule>

<rule name="if_maintainer_code_then_checksum_digit_valid_OPTIONAL_NULL">
<parameter identifier="clinic">
<java:class>br.atech.smssp.domain.clinic.vo.ClinicVO</java:class>

</parameter>
<java:condition>clinic.getMaintainerCode() == null</java:condition>
<java:consequence>
//Maintainer code is optional and can be NULL
ruleMessage.markRuleAsValid("if_maintainer_code_then_checksum_digit_valid");
</java:consequence>
</rule>

在此示例中,当要保存有关诊所的数据时,服务类将调用Drools规则引擎,并要求应用验证规则。 如果所有规则都通过,则可以安全地保存数据。 这些验证规则随政府法规的变化而变化。 规则的更改很频繁,因此将验证规则与代码分开很重要,因此无需重新启动应用程序即可进行维护。

得到教训

代码生成是该项目成功的关键因素。 它提高了开发人员的生产力,并使代码同质。 当您有50个开发人员在独立的团队中工作时,拥有同一个代码非常重要,他们全部共享相同的基本组件。 但是,一段时间后使用XDoclet会产生不良的副作用,因为生成时间太长了。 转向注释可以减少生成时间,并且仍然保留代码生成的好处。

良好的沟通至关重要。 在一个如此庞大的项目中,有不同的“部落”有时似乎会说不同的语言。 使他们了解彼此的观点是一项艰巨的任务,有时甚至是令人沮丧的任务。 这些部落之间的交流尤其麻烦:

  • 开发人员与建筑师-您必须吃自己的狗食。 当建筑师积极参与开发活动时,他们可以提出更好的解决方案并提高生产率。 这也有助于提高团队的“快乐程度”,这也会影响生产率。
  • 需求分析师/客户与开发人员/架构师-这两个团队之间的沟通失败经常会导致延迟和沮丧。 一个好的规范是必不可少的,但是了解这两个小组的动机并使他们作为一个团队行事,仅凭一个好的用例规范是无法解决的。
  • 非同寻常的开发人员与其他非同寻常的开发人员-该项目有一个非同寻常的开发人员团队,每个开发人员都怀着制作软件的好主意。 但是,在很短的时间内,团队无法检验每天都出现的所有革命性想法。 这给团队带来了一些挫败感。 在这样的项目中,重要的一点是要保持目标的明确定义,并让每个人都理解做出每个决定的原因。 在这方面有帮助的一件事是团队定期进行了数周的重构。 在这几周中,团队讨论了问题,在此期间可以进行哪些更改以解决这些问题并重构系统。 花费一个星期的时间进行重构,而不需要开发任何新代码,即可修复您的代码,这有助于使用新思想并提高生产率。

如果您拥有定义明确的体系结构,包括规则,代码生成,设计模式,组件,则在软件上进行中小型重构会更容易。 大的重构总是很难的。 但是,严格的规则也使得在架构不太适用的20%的应用程序中实施起来更加困难。 重要的是要知道在执行体系结构标准方面应该走多远。 有时,在项目进行过程中,开发人员将解决方案调整为适合体系结构的方法时,这并不是最佳方法。 另一方面,规则不是很严格的系统部分(例如在用户界面中),每个开发人员都选择了自己的解决方案,这会带来一些不良后果,例如系统部分的质量差以及维护工具的难度。码。

在大型应用程序中,当您具有大量依赖关系和代码时,开发周期往往会变长。 有时,在屏幕上添加一个简单的字段意味着更改系统的许多部分,这需要时间。 这使开发人员和客户都感到沮丧。 工具变得太沉重,一切似乎都花费了更多的时间。 该应用程序分为几个部分,但这不足以完全解决问题。 尽管J2EE具有优势,但它也给代码带来了更多的复杂性,并且在生产率方面付出了代价,这很难向管理人员和客户解释。

当团队开始这个项目时,许多人说在那个时间范围内是不可能的。 在其他国家,已经出现了许多沮丧的公共医疗信息系统建设经验。 因此,从这一经验中学到的最后一个教训:当人们说某事是不可能的时,不要让那阻止您使之成为可能。

未来发展方向

在容器外启用测试

确实令团队感到困扰的是他们无法在容器外部进行测试。 他们最初无法执行此操作的原因是因为他们被迫从一些旧代码开始。 政府规章模块是一个庞大的EJB 1.0系统。 应用程序的新部分越来越基于POJO,并且代码生成用于生成所有实体bean和其他容器工件。 但是,即使进行增量更改,部署到容器所花费的成本也要高于开发人员所愿意接受的。

转向基于POJO的体系结构

当前系统显然具有许多容器依赖项和EJB管道代码(尽管其中大多数都被生成的业务委托和会话外观隐藏)。 但是,他们计划转向的架构完全基于POJO,包括业务规则。 团队希望将所有会话bean重构为一个会话bean拦截器,然后将其委托给服务层。 会话bean拦截器的目的是轻松提供中间件服务,例如事务和线程管理。 他们曾经考虑过使用Spring,但是根据他们现有的体系结构,将其更改视为昂贵的。 由于它们的现有会话bean都已生成,因此很容易更改代码生成逻辑以通过会话bean拦截器,而不是完全移出EJB。 使这种重构成为可能的另一个因素是业务委托层的存在,该委托层将对Web层隐藏这些巨大的更改。

他们为什么不完全摆脱EJB? 原因是因为最终他们将被要求能够将其应用程序公开给卫生部的其他部门,而卫生部门需要访问这些系统,并且他们计划仅将人员(来自圣保罗市)交给企业可以开始使用的代表。 由于业务代表隐藏了Remoting的所有方面,并且他们知道其合作伙伴将使用Java进行工作,因此预期该集成将非常轻松。

他们将如何考虑不同的交易类型? 它们将具有具有不同事务划分的不同方法,并且具有目标方法与事务类型的映射,因此他们知道要执行哪个调用方法。 顺便提一句,在Floyd Marinescu的EJB Design Patterns一书中非常类似地描述了该会话bean拦截器和事务策略。 :)

最后,最终他们希望摆脱实体bean,甚至只是直接使用Hibernate持久化值对象。 他们可能面临的最大成本是测试。 他们必须彻底地测试事物,而单元测试根本无法捕获一切。

AJAX简化了Web UI

AJAX被认为是为非技术性最终用户简化UI工作流的工具。 例如,在调度用例上,自动完成可以帮助键入医生的姓名,程序名称,专业名称。 没有理由调用弹出窗口并进行查询,然后从大列表中选择。 同样,在处理大型长表单时,最好使用AJAX将表单字段的值逐步保存到HTTPSession中,以防万一有人错误地单击页面或丢失工作。

AJAX最近已经被部署。 AJAX已用于在巨大的过程树中导航,同时根据需要检索元素。 这使我们能够为用户提供更好的界面,并减少数据输入时间。 单击此处查看屏幕截图

翻译自: https://www.infoq.com/articles/Brasilian-Healthcare-System/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

巴西柔术

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值