Laravel程序 最佳实践1 作者: nbboy 时间: 2020-03-14 分类: PHP 评论 # 层的定义 Laravel程序在初始化后,其实相比PHP的其他框架,分层还算比较明确的,正如它的作者所说,是为艺术家写的框架。刚开始的时候,会把所有的代码写在Model里,当代码比较多的时候,往往一个Model代码文件就会膨胀开来。不但Model文件代码非常多,Controller文件代码也非常多,这又会导致复用率非常低。 所以后来在思考一种更好的分层,相信很多从静态语言走过来的读者,比如Java或者C#在开发WEB程序的时候,也是会经典的三层架构MVC。他们更习惯把逻辑代码写在Service层,这样既解决了复用的问题,也解决了把业务逻辑写在Controller层的尴尬。 这时很多人感觉有点疑问,为什么把代码写在Controller层不好?因为根据标准的MVC模型的定义,C这层存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。也就是说,如果把大段的业务逻辑,放在了控制层,不但影响到了复用度,而且导致各层的职责混乱! 说道MVC,简单复习下MVC的一些概念。其实MVC最早不是产生于WEB程序,而开始是存在于桌面程序中的,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图、饼图来表示。也就是按照标准的定义,我们很多人用的都不是MVC,而是变态版本的MVSC,中间加入一个Service层。这里也顺便说下,其实按照标准,M和V的数据变动是双向的,但是目前我们的WEB做不到这点。 再说回Laravel的情况,Laravel的模型层真的是非常舒服,借助于Eloquent ORM,写起查询来真的是一种享受。借用Laravel官方文档说:"Eloquent ORM 提供了一个漂亮、简洁的 ActiveRecord 实现来和数据库交互"。写过的肯定对他的链式写法映象深刻,这种做法是他的优点之一。有了一个足够强大的模型层,事情真的解决了一大半。**按照DDD的想法,模型应该反映领域驱动的模型,引入Repository层来解决模型的加载和存储问题,引入Service层来解决跨模型的事物处理问题。**对于一般的中小型项目,这种分层已经够用了。分层其实本身很简单,难得是大家都要去遵守这种约定和随着项目不停得去对代码和模块层级,类层级进行审视和重构。 # 框架的破坏 这个是一种糟糕的写法,其实第一次我看到这样写的时候,内心真的10万头草泥马在奔腾。在团队里的很多小伙伴,在Controller层写类似这种代码: AuthController.php中 ```php $token=User::createToken($oldUser); ``` User.php中 ```php protected function createToken($user){ ... } ``` Model类中的保护成员竟然在Controller层进行直接调用,说好的保护呢???不过回过头一想,业务程序员是缺乏基础的认识,但是框架怎么能放行这种愚蠢的写法呢!!! 看看框架做了什么?  哇,原来是这么一回事,果然是框架在作怪。不过仔细想想,作者的想法是不是就是这样?我猜作者的想法只是为了给使用者方便,不用再去"NEW"一个对象,但是的确在这里给使用者放行了这种行为。我想作者可能已经想到了这个问题,只是让使用者自己去做规范吧。 # 总结 其实这篇文字真的没写多少,一方面博客刚恢复访问,总得写点什么。另外一方面后期有很多文章正在构思,小弟文笔不怎么样,只求在这里做一个备忘吧,如果能帮到读者,那我很开心了。其实,Laravel总体来说真的是PHP中的好框架了,但是使用中有很多细节需要考虑和决策,甚至当项目大了之后,也有必要做一定的改造。
用scrapy抓取qunar门票信息 作者: nbboy 时间: 2019-11-20 分类: Python 评论 # 概述 水文一篇,本来想把scrapy做成一个系列,不过最近还是比较忙,等有空的时候,就工作中遇到的反爬机制说一下。这次说的是爬取qunar网站的景点信息,并且存储到mongodb中。在说一遍,python做爬虫是真的方便,指的是爬虫的前端部分。 # 准备工作 - 创建项目 ```python scrapy startproject ticket_spiders ``` - 创建爬虫 ```python scrapy genspider qunar qunar.com ``` # 制定爬取规则 通过分析网页我们找到了爬取qunar景区的入口接口,http://piao.qunar.com/ticket/list.json?keyword=浙江®ion&page=151&from=mps_search_suggest ,这里为了做演示,只爬取浙江的景区。起始URL填写为到start_urls中,因为内容返回为JSON的,所以解析步骤相对来说简单多了,直接解析为JSON对象就可以。在做了简单的判断之后,我们把抽取到的数据发往pipeline就可以了。 ```python result = json.loads(response.text) if 'ret' in result and result['ret'] == True: data = result['data'] oldKeyword = data['keyword'] if oldKeyword == '浙江': sightList = data['sightList'] if sightList: for sight in sightList: yield QunarItem(**sight) ``` # 分页规则 爬虫的分页规则每个爬虫都不一样,针对我们今天的爬虫而言,我们只要让他一页一页翻下去就可以了。parse函数里的代码: ```python def parse(self, response): page = response.meta.get('page', 151) self.logger.info('current page {page}'.format(page=page)) result = json.loads(response.text) if 'ret' in result and result['ret'] == True: data = result['data'] oldKeyword = data['keyword'] if oldKeyword == '浙江': sightList = data['sightList'] if sightList: for sight in sightList: yield QunarItem(**sight) nextPage = page + 1 yield scrapy.Request( url='http://piao.qunar.com/ticket/list.json?keyword=%E6%B5%99%E6%B1%9F®ion&from=mps_search_suggest&page=' + str( nextPage), callback=self.parse, meta={'page': nextPage}) else: self.logger.error('被限制爬行了') ``` # 制定处理规则 到了这步,我们已经有数据了,接下来该清洗的清洗,该处理的处理,该存储的存储,这次爬虫目标很简单,就是把数据全部存储起来就可以。 ```python import pymongo mongoClint = pymongo.MongoClient('127.0.0.1', 27017) dbQunar = mongoClint['qunar'] collection = dbQunar['sight'] class QunarPipeline(object): def process_item(self, item, spider): collection.insert_one(item._values) return item ``` # 全局配置 上面那步写好了处理器,但是并没有进行挂接,这步我们进行挂接,在settings.py文件里,更改处理器配置参数如下: ```python ITEM_PIPELINES = { 'ticket_spiders.pipelines.QunarPipeline': 300, } ``` # 运行和结果 ```shell scrapy crawl qunar ``` 执行后就可以得到最终抓取的结果,嗯,挺有满足感的,下面可以用这些数据进行分析或者展示了。  # 总结 scrapy作为抓取引擎既简单又强大,简单是说他其实封装了很多复杂的机制和概念,下次我带领大家从源码角度去了解这个框架。学习一门技术要从根本上,特别是源码角度或者是核心模型角度去了解其实现,才能从瞬息万变的技术变革中以不变应万变,以此和读者共勉。
设计模式之外观模式(二) 作者: nbboy 时间: 2019-08-11 分类: 软件架构,软件工程,设计模式 评论 ###简介 外观模式主要解决封装复杂逻辑的问题,也一定程度封装了变化的部分,对外提供一致的接口。具体实际中也用的比较多,比如Laravel框架中就用到外观模式去封装服务容器中底层类的 「静态代理」。我一般用在三方系统的接口封装上,比如三方接口封装不合理或者过于复杂,我就会用外观模式封装然后提供给内部系统使用。思想很简单,不多介绍了。 ###结构图和源码 借用一张图来表示下:  可以看到左边是复杂的具体实现类,右边下面就是外观类,其封装了左边的实现类。 具体实现可以看我的代码[study_design_pattern](https://github.com/x-debug/study_design_pattern "study_design_pattern"),在facade包下。
设计模式之简单工厂模式(一) 作者: nbboy 时间: 2019-06-07 分类: 软件架构,软件工程,设计模式 评论 ###简介 简单工厂模式就是用一个工厂函数封装具体的对象初始化过程,通过参数来控制需要具体初始化的对象。这里不容易变的部分是接口定义部分,比如例子中的Shape部分,容易变的部分是Rectangle,Circle等这些实现,要扩展的时候,根据需求增加不同的实现就可以做到无限扩展。 ###结构图与代码 用图表示大概是这样的:  可以看到,客户端在使用的时候,只需要调用工厂函数就可以创建具体实现,其依赖的也是接口。这就是针对接口去编程,而不是具体实现。 具体实现可以看我的代码[study_design_pattern](https://github.com/x-debug/study_design_pattern "study_design_pattern"),在simple_factory包下。
领域驱动设计漫谈(1) 作者: nbboy 时间: 2019-05-30 分类: 软件架构 评论 ## 前言 随着微服务的兴起,DDD领域驱动设计又开始被很多人提起来。最近,我也在了解这方面的知识,所以对软件设计方法做一个简单总结,但是相关书还没全部看完,可能理解不深刻,等看完再做一个总结。 ## 问题 先说下在没有经过领域驱动设计时软件设计方法,也就是我们平时使用的数据库驱动设计方法。当接到部门领导提的需求,或者产品经理的文案,先开始脑子里想程序如何设计,一般程序员先定义表结构,考虑表结构细节,然后这个过程中根据产品需求,不断将设计靠近需求。这种设计方法当然没有任何问题,只要程序员经验足够丰富,往往也能收到不错的效果。在业务初期,我们的功能大都非常简单,普通的CRUD就能满足,此时系统是清晰的。随着迭代的不断演化,业务逻辑变得越来越复杂,我们的系统也越来越冗杂。可以想象,一个跨越6,7年,前后程序员换了3,4批的项目,可能业务方说需要加一个简单的需求,但是可能由于改动非常多,做起来痛苦不堪;或者改一处必须导致全部相关的UserCase都得重新通过测试。 还记得我刚入行WEB程序的时候(因为之前是做游戏开发的),对通过表结构思考程序设计很不习惯。我更喜欢从OO角度思考问题,我当时甚至这样想,难道WEB程序不能OO设计么?看到很多同事所谓的对象化,也只是带过程化的对象,很不习惯,不自然,用DDD作者的话说就是贫血模型或者说失血模型。这种设计,往往随着业务复杂,人员变更,程序越来越让你难以理解,模块依赖性紊乱,模块层次错杂,原来应该写在核心层的业务逻辑却被写在控制器层。就如下面这幅图一样:  ## DDD的关键元素 ### 战略建模 战略和战术设计是站在DDD的角度进行划分。战略设计侧重于高层次、宏观上去划分和集成限界上下文,而战术设计则关注更具体使用建模工具来细化上下文。那如何进行战略建模呢?现实世界中,领域包含了问题域和解系统。一般认为软件是对现实世界的部分模拟。在DDD中,解系统可以映射为一个个限界上下文,限界上下文就是软件对于问题域的一个特定的、有限的解决方案。 这里引出了限界上下文的概念,那什么是限界上下文呢?概况起来说:**一个由显示边界限定的特定职责。领域模型便存在于这个边界之内。在边界内,每一个模型概念,包括它的属性和操作,都具有特殊的含义。**不过,这个定义很官方,我的理解就是软件设计中模块的概念,比如订单子域是一个模块,商品子域又是另外一个模块。为什么要模块的封装概念?从软件设计上我们应该能很好理解,把系统分成由更多子系统显然比一个大系统更加好管控。从微服务拆分角度说,一个微服务进程就是一个限界上下文,也往往可能是大公司里的一个小团队负责。模块的拆分是一个问题,另外一个问题是模块之间的联系,或者说模块与模块的沟通问题,比较幸运的是前人总结了这方面的经验: **限界上下文之间的映射关系** - 合作关系(Partnership):两个上下文紧密合作的关系,一荣俱荣,一损俱损。 - 共享内核(Shared Kernel):两个上下文依赖部分共享的模型。 - 客户方-供应方开发(Customer-Supplier Development):上下文之间有组织的上下游依赖。 - 遵奉者(Conformist):下游上下文只能盲目依赖上游上下文。 - 防腐层(Anticorruption Layer):一个上下文通过一些适配和转换与另一个上下文交互。 - 开放主机服务(Open Host Service):定义一种协议来让其他上下文来对本上下文进行访问。 - 发布语言(Published Language):通常与OHS一起使用,用于定义开放主机的协议。 - 大泥球(Big Ball of Mud):混杂在一起的上下文关系,边界不清晰。 - 另谋他路(SeparateWay):两个完全没有任何联系的上下文。 ### 战术建模 梳理清楚上下文之间的关系后,我们需要从战术层面上剖析上下文内部的组织关系。首先看下DDD中的一些定义。 ##### 实体 当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。 ##### 值对象 当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。 ##### 聚合根 Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。 聚合根这个概念稍微解释下,听起来其实很抽象,我自己的理解是把几个逻辑上相关的对象(实体/值对象)有意的组织在一起,并且对外只提供根实体的访问,内部维护状态的一致性。 ##### 领域服务 一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。 ##### 领域事件 领域事件是对领域内发生的活动进行的建模。 ## 实践DDD 未实现 ## 总结 DDD驱动设计思想作为一种领域建模的设计思想指导我们进行灵活设计,让我们的系统更加有弹性和充血。这个过程当然不是通过前期的一次设计就能完成的,而是通过不断和领域专家沟通,并且同步领域模型和代码模型,重构之前的不合理部分代码,然后才能达到合理的设计。