架构之路(五):忘记数据库

本文探讨了为何在系统架构设计中选择面向对象而非面向数据库,以及如何通过假设系统不使用关系数据库来实现这一转变。重点阐述了抽象、解耦、复用等概念在面向对象设计中的优势,强调了在复杂系统中采用面向对象方法的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

allowtransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?url=http%3A%2F%2Fwww.youkuaiyun.com%2Farticle%2F2015-10-17%2F2825942&type=3&count=&appkey=&title=%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%88%96%E8%80%85%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%EF%BC%8C%E6%9C%80%E9%87%8D%E8%A6%81%E4%B8%80%E7%82%B9%E5%B0%B1%E6%98%AF%E8%A6%81%E5%BF%98%E8%AE%B0%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%81%E6%88%91%E8%8A%B1%E4%BA%86%E5%BE%88%E9%95%BF%E5%BE%88%E9%95%BF%E7%9A%84%E6%97%B6%E9%97%B4%EF%BC%8C%E6%89%8D%E7%90%86%E8%A7%A3%E4%BA%86%E8%BF%99%E4%B8%80%E7%82%B9%EF%BC%8C%E4%BB%8E%E8%80%8C%E7%9C%9F%E6%AD%A3%E7%9A%84%E8%BF%88%E5%90%91%E4%B8%80%E4%B8%AA%E5%B4%AD%E6%96%B0%E7%9A%84%E5%A4%A9%E5%9C%B0%EF%BC%9B%E8%80%8C%E5%90%8E%EF%BC%8C%E6%88%91%E5%8F%88%E8%8A%B1%E4%BA%86%E5%BE%88%E9%95%BF%E5%BE%88%E9%95%BF%E7%9A%84%E6%97%B6%E9%97%B4%EF%BC%8C%E6%89%8D%E5%8B%89%E5%BC%BA%E5%81%9A%E5%88%B0%E8%BF%99%E4%B8%80%E7%82%B9%EF%BC%9B%E6%88%91%E5%B8%8C%E6%9C%9B%EF%BC%8C%E6%9C%89%E4%B8%80%E5%A4%A9%E8%BF%99%E5%B0%86%E4%B8%8D%E5%86%8D%E6%98%AF%E4%B8%80%E4%B8%AA%E9%97%AE%E9%A2%98%E3%80%82&pic=&ralateUid=&language=zh_cn&rnd=1445403830343" width="22" height="16" style="color: rgb(51, 51, 51); font-family: Helvetica, Tahoma, Arial, sans-serif; font-size: 14px; line-height: 24px;">摘要:面向对象或者领域驱动,最重要一点就是要忘记数据库!我花了很长很长的时间,才理解了这一点,从而真正的迈向一个崭新的天地;而后,我又花了很长很长的时间,才勉强做到这一点;我希望,有一天这将不再是一个问题。

本文为他倾囊相授的第五篇:


    前面写了这么多,很大程度上就是为了这一章做准备。面向对象或者领域驱动,最重要的一点就是要忘记数据库!我花了很长很长的时间,才理解了这一点,从而真正的迈向一个崭新的天地;而后,我又花了很长很长的时间,才勉强做到这一点;我希望,有一天,这将不再是一个问题,我不需要考虑这一点……

    为什么业务层这么薄

    三层架构流行起来之后,我们很清楚的知道UI层负责页面交互并调用下一层,也知道DAL层就是和数据库打交道。但BLL层?什么才算是“业务逻辑”?有各种各样的解释,但这些不都是SQL做的么?对于绝大多数的应用系统而言,除了对数据库进行“增删改查”以外,实在不知道还能做些什么?更何况,不是还有超级强大的存储过程么!

    所以,很多系统,即使勉强弄出一个业务层,也“薄”得不像话,像一层塑料薄膜一样,让人有一种把它立即撕掉的强烈的冲动。

    为什么会是这样呢?

    这得从.NET阵营从历史说起。.NET阵营的同学知道三层架构,多半是从PetShop开始,这被奉为三层架构的经典,很多项目甚至是直接复制其架构。在当时,它是一种了不起的进步。那时候,还是从ASP向ASP.NET转型的过程,很多SAP项目,SQL代码都还是写在HTML里面的!所以,UI和DAL的分离,无疑具有明细的示范效应。 

    但微软的步子,不大不小,刚好扯着蛋。

    步子小一点,做成两层架构,估计一点问题都没有,大家都能接受;步子再大一点,就得上ORM,可惜微软当时还没条件支持。所以就搞出了这么个不明不白稀里糊涂的概念出来,折磨了我好久好久……

    长期以来,.NET的阵营,在应用级层面,其实是“面向数据库”的。从DataSet、DataGrid、DataSourceBinder之类的,都可以看出来。即使是Entity Framework,最开始也是从数据库的表向.NET的类进行映射。这些,都极大的制约了.NET阵营同学面向对象的思维拓展。

    好在我终于跳出来了。

    面向数据库

    为了说明,我们举一个最简单的例子。

    需求是:记录文章(Article)的浏览数(ViewCount)。每当文章被阅读(View)一次,浏览数加一。

    看到这个需求,你首先想到的是什么?是不是:

    Update Article set ViewCount = ViewCount + 1;
    如果是这样的话,恭喜你,你还牢牢的守住了“面向数据库”的阵地。

    /*
     
    面向数据库并不是不可接受的,面向对象也并不一定比面向数据库“高级”。
    这只是两条道路的选择,如果你愿意看一看另外一条路的风景,就请继续;否则,就此打住吧。
     
    */

    面向对象

    public void View()
    {
        //从数据库中取出Article
        Article article = session.Load<Article>(articleId);
         
        //改变Article的ViewCount属性
        article.ViewCount += 1;
     
        //将改变后的Article再存入数据库
        session.Save(article);
    }

    有什么感觉?眼前一亮,还是不可思议?想得更深一点的,是不是觉得这是多此一举,一句sql就能解决的问题,搞得这么复杂?

    我当年,考虑最多的,最不能接受的,是 性能问题。

    1. 这必须利用ORM,即使不考虑ORM生成的sql高不高效,就这生成sql的开销,应该就不低吧?
    2. 这样做,取数据,打开一次数据库连接;存数据,又打开一次数据库连接。就算有连接池,但能省一点就省一点不是更好?

    所以,如果你也和我一样,倒回去看我之前的博客吧!

    这样做,还有其他很多具体的技术问题,我们后续博客会逐一展开说明。

    为什么

    我们还是回到大方向上来,为什么要这么做?换言之,“面向数据库”有什么问题,或者说“面向对象”有什么好处?

    我觉得,“抽象”、“解耦”、“复用”之类的说法,都还没有触及根本。最根本的原因,还在于我们的大脑,我们的大脑不适应于把这个世界抽象成一张一张的表,而更适应于一个一个的对象。随着系统日趋复杂,这种现象就表现得越明显。

    我曾经参与过一个项目,它的数据库结构打印出来,得用地图那么大一张纸(我不知道算A几了),密密麻麻的全是表,各种线条交错其中,我看着就头皮发麻。部门里面像个宝贝一样把这张表供着,因为公司没法打印也没法复印啊!(我不知道他们最开始是怎么得来的,估计肯定麻烦)

    如果你一边读一边在想,就会发现,“不对呀,有多少表就有多少类,类图不是一样复杂吗?”

    是的,而且由于抽象,类很可能比表还要多。但是,有于抽象,在我们进行架构、设计、沟通的时候,可以暂时的抛弃很多细节。比如,我们可以说,“文章 被评论之后,文章作者的积分加10分”,这个时候,我们就不考虑文章有很多种:博客、新闻、问答、评论……,也不考虑积分增加是直接改积分总分呢,还是添 加一条积分记录,或者还要同步……。如果只有表,你怎么说?

    当然,表的结构也可以设计成类似于继承的样子(类的继承关系也最终会映射成表结构),但是,在交流沟通中,你如何表明这种抽象关系呢?

    单纯从程序的角度上说,使用ORM,面向对象,还增加了系统的复杂性。毕竟多了一道工作,而且把对象映射到数据库不是一件简单的工作,尤其是你还要考虑性能问题的时候。

    那为什么我们还要这样做?委托,换言之,把复杂性往其他地方推。我记得我反复讲过这一点,架构的一个重要工作,就是把复杂性进行拆分和推诿。拆分估计大家好理解,但“推诿”是个什么意思,推给谁呢?管它呢,我只做我分类的事,其他的,UI推给BLL,BLL推给DAL,DAL推给DBA,DBA推给采购部……

    写在这里很搞笑,但事实就是这样的。在 性能篇,我说,你要写高性能的代码,你就是抢了人家的饭碗,就这个意思。UI都把DBA的活儿干了,人家吃什么?你代码都写成01001010101010二进制了,别说做汇编的,估计做CPU的都活不下去了。

    我们这里,就是把复杂度推给了Map团队、ORM工具开发商和DBA。

    因为我们要和客户谈需求啊,典型的是领域驱动,要和客户/领域专家找到“共同的语言”,这共同的语言是什么?是表结构?估计如果开发的是一个财务会 计系统,这还是可行的——估计早期的系统大多就是财务报表类系统?说不定还真是这样。为什么面向对象从Java开始流行,Java是虚拟机,可以用在微波 炉报警器之类上面的,底层数据结构可以完全脱离数据库啊!.NET做什么起家的,就报表啊!呵呵。

    总之,发展到今天,随着系统复杂性的增加。在系统的架构设计中,我们不得不将现实世界首先映射成一个一个可以封装、具有继承多态特性的对象,并且将重心放在这些对象关系功能的维护上。

    数据库?就先不管它吧。

    只有脱离了数据库的束缚,我们才能自由的翱翔在面向对象的世界里!

    忘不掉

    “问题是我忘不掉啊!”

    “我只要看到需求,脑子里马上就是数据库就是表。”

    “没有数据库,我都不知道怎么开始写代码了。”

    ……

    是的,忘掉数据库是很难很难——尤其是对于我们这些老人来说。已经浸淫sql数十年的高手,你让我忘掉它?你以为写小说啊,张无忌学太极啊?

    我只能说说我是怎么做到的,希望能给你一些参考。

    我就假设我的系统不是用“关系数据库”存储数据,不是mysql,不是oracle;我用nosql,我用xml文件存储,行不行?nosql,怎 么用?不知道啊,我十窍通了九窍。但我就要在我还不知道nosql怎么用的时候,就开始构建我的BLL/领域层。而且我只设定几个最简单的假设:

    1. 所有的对象都可以直接从硬盘Load()出来
    2. 所有的对象都可以直接Save()到硬盘
    3. 对象之间用1:1、1:n、n:n建立关联即可
    究竟怎么从硬盘里存取(所谓的“持久化”),以后再说。我连用什么进行持久化都不知道,现在怎么考虑?但有一条,反正不会用关系数据库,估计是用NoSQL吧…… 

    最终的期望

    真正的对象数据库!快出来啊,求你了…… 

    惯例说我的项目进展: 
    1、写文档写到吐…… 
    2、重构累成了狗…… 
    本计划发布了新版本再写这篇博客的,但实在不能再拖了。博客系列接下来,就进入项目的具体开发了,代码还乱成一堆,啊…… 

    CH341A编程器是一款广泛应用的通用编程设备,尤其在电子工程和嵌入式系统开发领域中,它被用来烧录各种类型的微控制器、存储器和其他IC芯片。这款编程器的最新版本为1.3,它的一个显著特点是增加了对25Q256等32M芯片的支持。 25Q256是一种串行EEPROM(电可擦可编程只读存储器)芯片,通常用于存储程序代码、配置数据或其他非易失性信息。32M在这里指的是存储容量,即该芯片可以存储32兆位(Mbit)的数据,换算成字节数就是4MB。这种大容量的存储器在许多嵌入式系统中都有应用,例如汽车电子、工业控制、消费电子设备等。 CH341A编程器的1.3版更新,意味着它可以与更多的芯片型号兼容,特别是针对32M容量的芯片进行了优化,提高了编程效率和稳定性。26系列芯片通常指的是Microchip公司的25系列SPI(串行外围接口)EEPROM产品线,这些芯片广泛应用于各种需要小体积、低功耗和非易失性存储的应用场景。 全功能版的CH341A编程器不仅支持25Q256,还支持其他大容量芯片,这意味着它具有广泛的兼容性,能够满足不同项目的需求。这包括但不限于微控制器、EPROM、EEPROM、闪存、逻辑门电路等多种类型芯片的编程。 使用CH341A编程器进行编程操作时,首先需要将设备通过USB连接到计算机,然后安装相应的驱动程序和编程软件。在本例中,压缩包中的"CH341A_1.30"很可能是编程软件的安装程序。安装后,用户可以通过软件界面选择需要编程的芯片类型,加载待烧录的固件或数据,然后执行编程操作。编程过程中需要注意的是,确保正确设置芯片的电压、时钟频率等参数,以防止损坏芯片。 CH341A编程器1.3版是面向电子爱好者和专业工程师的一款实用工具,其强大的兼容性和易用性使其在众多编程器中脱颖而出。对于需要处理25Q256等32M芯片的项目,或者26系列芯片的编程工作,CH341A编程器是理想的选择。通过持续的软件更新和升级,它保持了与现代电子技术同步,确保用户能方便地对各种芯片进行编程和调试。
    内存分区情况的分析是嵌入式系统开发中的一个重要环节,特别是在资源有限的MCU(微控制器)环境中。标题提到的工具是一款专为分析Linux环境下的`gcc-map`文件设计的工具,这类文件在编译过程结束后生成,包含了程序在目标设备内存中的布局信息。这个工具可以帮助开发者理解程序在RAM、ROM以及FLASH等存储区域的占用情况,从而进行优化。 `gcc-map`文件通常包含以下关键信息: 1. **符号表**:列出所有定义的全局和静态变量、函数以及其他符号,包括它们的地址和大小。 2. **节区分配**:显示每个代码和数据节区在内存中的位置,比如.text(代码)、.data(已初始化数据)、.bss(未初始化数据)等。 3. **内存汇总**:总览所有节区的大小,有助于评估程序的整体内存需求。 4. **重定位信息**:显示了代码和数据如何在目标地址空间中定位。 该分析工具可能提供以下功能: 1. **可视化展示**:将内存分配以图形化方式呈现,便于直观理解。 2. **详细报告**:生成详细的分析报告,列出每个符号的大小和位置。 3. **比较功能**:对比不同编译版本或配置的`map`文件,查看内存使用的变化。 4. **统计分析**:计算各种内存区域的使用率,帮助识别潜在的优化点。 5. **自定义过滤**:允许用户根据需要筛选和关注特定的符号或节区。 虽然在MCU环境中,Keil IDE自带的工具可能更方便,因为它们通常针对特定的MCU型号进行了优化,提供更加细致的硬件相关分析。然而,对于通用的Linux系统或跨平台项目,这款基于`gcc-map`的分析工具提供了更广泛的适用性。 在实际使用过程中,开发者可以利用这款工具来: - **优化内存使用**:通过分析哪些函数或数据占用过多的内存,进行代码重构或调整链接器脚本以减小体积。 - **排查内存泄漏**:结合其他工具,比如动态内存检测工具,查找可能导致内存泄漏的部分。 - **性能调优**:了解代码执行时的内存分布,有助于提高运行效率。 - **满足资源限制**:在嵌入式系统中,确保程序能在有限的内存空间内运行。 总结来说,`gcc-amap`这样的工具对于深入理解程序的内存布局和资源消耗至关重要,它能帮助开发者做出更明智的决策,优化代码以适应不同的硬件环境。在处理`map`文件时,开发者不仅能获取到程序的内存占用情况,还能进一步挖掘出可能的优化空间,从而提升系统的整体性能和效率。
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值