从传统数据结构迁移到新的数据结构

27

好的,这是我们正在面临的问题。

目前:

  1. 我们有大量直接访问数据库的遗留应用程序
  2. 数据库中的数据结构未经规范化
  3. 几乎所有应用程序都使用当前的流程/结构

我们试图实现的:

  1. 将所有功能移动到RESTful服务中,以便没有应用程序直接访问数据库
  2. 实现规范化的数据结构

我们遇到的问题是如何实现此迁移,不仅涉及应用程序,还涉及数据库。

我们目前的解决方案是:

  1. 识别所有CRUD功能,并在新Web服务中实现它们
  2. 创建新的应用程序来替换旧的应用程序
  3. 将新应用程序指向新Web服务(仍然指向旧的数据结构)
  4. 将数据库中的数据迁移到新的结构中
  5. 将新应用程序指向新的Web服务(指向新的数据结构)

但是,在讨论此过程时,我们正在考虑需要两次重写新的Web服务。一次是为了旧数据结构,一次是为了新数据结构,因为目前我们无法表示旧数据结构以适应新的Web服务。

我想知道是否有人面临过这种挑战,以及如何克服这些问题/实现等。


关于“构建Web服务两次”部分,只是一个快速的提醒。考虑在Web服务后面构建一个服务层 - 例如类库 - 并将对象层次结构和接口等与单独的CRUD层一起使用以“填充”对象(使用工厂/存储库模式和业务层)。在您的Web API中引用服务层。一旦新的DB结构就位,您只需要替换实际访问代码(您可以使用DI/IoC查看提供程序模式)层,而无需修改更高的业务/服务和Web层。 - Luke Baughan
3个回答

20

编辑:更多有关使用双向触发器同步的解释;更新了语法、语言和清晰度。

前言

我在一家大型网络应用程序上工作了7年,面临着类似的数据模型升级问题,所以我理解你的困境。基于这种经验,我提出了一些有点不同但希望更容易实施的建议。首先,有一个观察:

对于组织而言,价值在于数据——数据将长时间存在,超越所有当前应用程序的生命周期。企业将不断发明新方法来从捕获的数据中获取价值,这将产生新的报告、应用程序和业务方式。

因此,正确地获取新的数据结构应该是最重要的目标。不要为了其他短期开发目标(特别是以下内容)而牺牲获取正确的结构:

  • 操作性目标,如推出新服务
  • 报告性能(使用材料化视图、触发器或批处理作业替代)

这个结构会随着时间的推移而改变,因此您的架构必须允许频繁添加和不经常正常化它。这意味着您的数据结构和任何共享的API(包括RESTful服务)必须得到适当的版本控制。

为什么使用RESTful Web服务?

你提到你将“将所有功能移动到RESTful服务中,以便没有应用程序可以直接访问数据库”。我需要就传统应用程序提出一个非常重要的问题:这很重要吗?它带来了什么价值?

我之所以这样问,是因为:

  • 您失去了ACID事务(除非您实现一些可怕复杂的WS-*标准,否则每个调用都是单个事务)
  • 性能下降:直接连接数据库将更快(没有Web服务器工作和翻译需要),并且延迟较小(通常为1ms而不是50-100ms),这将显著地降低应用程序对直接DB连接编写的响应性
  • 数据库结构并没有从RESTful服务中抽象出来,因为您承认随着数据库规范化,您必须重新编写Web服务并重写调用它们的应用程序。

而其他交叉关注点则没有改变:

  • 可管理性: 直接数据库连接可通过许多通用工具进行监控和管理
  • 安全性: 直接连接比您的开发人员编写的 Web 服务更安全
  • 授权: 数据库权限模型非常先进,可以细致到你想要的程度
  • 可扩展性: Web 服务是一个(仅有的?)直接连接的数据库应用程序,因此只能像数据库一样进行扩展

您可以通过维护遗留 RESTful API 来迁移数据库并保持遗留应用程序运行。但是,如果我们不引入“遗留”RESTful服务,而是保持遗留应用程序呢。

数据库版本控制

假设大部分“遗留”应用程序使用 SQL 直接访问数据表; 您可能还有许多数据库视图。

一种数据迁移方法是:新数据库(以新架构中的新范式结构为基础)将旧结构作为< strong>视图 向遗留应用程序呈现,通常来自不同的模式。

实际上这很容易实现,但仅解决报告和只读功能。那么遗留应用程序 DML 呢?DML 可以使用以下方式解决:

  • 可更新视图用于简单转换
  • 引入存储过程(如果不支持可更新视图)(例如 "CALL insert_emp(?, ?, ?)" 而不是 "INSERT INTO EMP (col1, col2, col3) VALUES (?, ? ?)")
  • 使用触发器和 DB 链接具有与新数据库同步的“遗留”表

具有双向同步功能的遗留格式表,使用触发器与新格式表进行同步是一种粗暴的解决方案,相对丑陋。您最终会在两个不同的模式(或数据库)中拥有相同的数据,并且如果同步代码存在错误,则可能导致数据不同步 - 然后您就会面临“两个主人”的经典问题。因此,请将其视为最后的手段,例如当:

  • 基本结构已更改(例如更改关系的基数),或
  • 将数据转换为旧版格式是一个复杂的函数(例如,如果旧版列是新格式列值的平方并设置为“4”,可更新的视图无法确定正确值是+2还是-2)。

当需要对数据进行这种更改时,代码和逻辑会有一些重大变化。您可以实现兼容性层(优点:不更改旧代码)或更改旧应用程序(优点:数据层干净)。这是工程团队的技术决策。

使用上述方法创建遗留结构的兼容性数据库可最大限度地减少对旧应用程序的更改(在某些情况下,旧应用程序甚至不需要进行任何代码更改)。这极大地降低了开发和测试成本(对于业务来说没有净功能收益),并极大地降低了推出风险。

它还使您能够集中精力关注组织的真正价值:

  • 新的数据库结构
  • 新的RESTful web服务
  • 新应用程序(可能使用RESTful web服务构建)

Web服务的积极方面

请不要将上述内容解读为反对web服务,特别是RESTful web服务。当用于正确的原因时,例如用于启用web应用程序或在不同系统之间进行集成时,这是一种良好的架构解决方案。然而,在数据迁移期间管理您的旧应用程序可能并非最佳解决方案。


有一个“遗留”表,通过触发器和数据库链接与新数据库同步。这不是算有两个主数据库吗?对于问题的深刻洞察力给予+1,谢谢。 - Phill Pafford
伟大的观点!使用触发器进行双向同步是一种hack(它可能很丑陋),但对于关系或列的基数变化较大的表格来说,这可能是有意义的。(在这些情况下,大的变化是不可避免的,而将其构建到兼容性层中还是将其改造为旧应用程序则是一个技术决策。啊,应用程序维护,太棒了 :) 我已经更新了我的答案,并加入了更多细节。 - Andrew Alcock
希望还有人在这里 :) -- 我也面临同样的挑战,但对数据库非常陌生。你能否更详细地解释一下“兼容层”是什么?我不确定它是什么,一个类库吗?那将需要改变所有旧应用程序。感谢您的回答。 - dune.rocks
1
@dune.rocks:抱歉,我今天才看到你的问题。兼容性层是解决方案的一部分,为传统应用程序提供了一个数据库视图,该视图看起来与原始表和视图集合相同或接近。兼容性层是这个传统结构和新的规范化结构之间的映射,并在其实现中使用视图、存储过程、触发器等。这个解释对你有帮助吗? - Andrew Alcock
@AndrewAlcock,是的,我明白你的意思。我是否正确地说,这需要更改所有遗留应用程序中的DML语句以使用存储过程?(在我的情况下,新模式有很大不同) - dune.rocks
显示剩余2条评论

2
看起来你需要做的是定义一个新的数据模型(“标准化”),并建立从标准化模型到遗留模型的映射。然后,您可以随意将遗留直接调用替换为对标准化模型的调用。这不会破坏任何代码。
同时,您需要定义相当于(集中式)遗留数据库API,并将其映射到标准化模型。现在,您可以随意使用遗留数据库API调用替换原始的遗留数据库调用。这不会破坏任何代码。
一旦完全替换了原始调用,就可以将数据模型切换到真正的标准化模型。这应该不会破坏任何代码,因为现在所有内容都针对遗留数据库API或标准化数据库API。
最后,您可以使用修订后使用标准化数据API的代码替换遗留数据库API调用和相关代码。这需要仔细重新编码。
为了加快所有这些过程,您需要一个自动化的代码转换工具来实现代码替换。
这篇文章似乎有一个很好的概述:http://se-pubs.dbs.uni-leipzig.de/files/Cleve2006CotransformationsinDatabaseApplicationsEvolution.pdf

我们考虑过这种方法,但新的数据结构与旧的数据结构大不相同,我甚至不确定这是否可能。例如:在新的数据结构中,假设我们有一个可以通过ID引用的地址对象,在旧的数据结构中不存在这样的实体,因为这些信息将与人相关联,它们之间没有直接关系。你有什么想法? - Phill Pafford
你能否进一步详细说明“自动化代码转换工具”? - Phill Pafford
如果数据是等价的,那么必须存在代数等价。这可能不容易想到,但使用理解这种代数等价的工具来完成比与对关系一无所知的程序员重新编码要更有效。如果新数据不是代数等价的,则根据定义,新程序与旧程序的功能不匹配...这对您的组织意味着什么? - Ira Baxter
请查看我的简介,了解我为什么对此话题有所保留。既然您问了,可以参考我们的DMS软件重构工具包的描述:www.semanticdesigns.com/Products/DMS/DMSToolkit.html。 - Ira Baxter
想要理解数据等价性,你需要了解所谓的数据代数。这些是针对数据“类型”(sorts)的运算符系统[具有签名]和约束运算符必须执行的公理。 (计算机程序接口具有签名和“类型”部分,但没有公理)。代数学家会说,如果您可以通过“同态”(将类型映射到类型并将运算符映射到运算符组合)而不违反原始代数的公理来替换一个代数,则两个代数是等价的。... - Ira Baxter
你正在考虑的数据转换最好具有这个属性,否则你计算出的结果将具有不同的语义,从而破坏你的应用程序。这个态射可能非常复杂,并包括额外的项目(比如你的“对象地址”),只要原始属性(公理)得到保留即可。为了确保你已经正确地完成了这个过程,最好将原始数据模式(“数据代数”)转换为新的数据模式(这实际上是一个态射),从而保证等价性。然后,你需要修改你的代码以匹配新的数据模式... 可能需要使用工具来完成。 - Ira Baxter

1

首先,这似乎是一个非常混乱的情况,我不认为有一个“干净”的解决方案。我曾经经历过类似的情况几次——它们并不好玩。

首先,更改客户端应用程序的工作量将是巨大的——如果基础域发生变化(例如引入与人分开的地址的概念),客户端应用程序也会发生变化——这不仅仅是访问数据方式的变化。避免这种痛苦的最佳方法是编写您的 API 层以反映未来的业务域模型,并将旧的数据库架构粘合到其中;如果有新的概念您无法使用旧数据反映(例如“get /app/addresses/addressID”),则抛出 NotImplemented 错误。在您可以使用旧数据反映新模型的地方,尽可能将其连接起来,然后在幕后进行重构。

其次,这意味着您需要将版本控制作为API的一级关注点 - 这样您就可以告诉客户端,在版本1中,功能x、y和z会抛出“NotImplemented”异常。每个版本都应向后兼容,但添加新功能。这样,您可以在版本1中重构功能,只要不破坏服务,并在版本1.1中实现功能x,在版本1.2中实现功能y等。最好为您的版本制定路线图,并在停止支持某个版本或发布破坏性更改时通知客户端应用程序所有者。
第三,为您的API编写一组自动化集成测试是您可以进行的最佳投资 - 它们确认您在重构时没有破坏功能。
希望这对您有所帮助 - 我认为您的问题并没有单一而直接的答案。

谢谢,这确实给了我一些关于实现的想法 +1 - Phill Pafford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接