Laravel : 使用动作类整理你的代码(翻译)

友偶阅网文一篇,头中尾英,阅不能,遂中译之。原文链接 :Keeping your Laravel applications DRY with single action classes

“这段代码该放在哪里?”恐怕是在谈论应用结构时最经常提起的问题。“我应该放在 Controller 里吗?还是 Model ?还是哪里?”,虽然 Laravel 是个十分灵活的框架,但要解答这个问题也不总能简单明了。

当你知道你的应用程序只有一个接入点的时候,把逻辑写在 Controller 是完全没问题的。但如今应用一般都会有好几个接入点共用同一个功能。

例如,很多应用都会有一个用户注册的表单,提交到 Controller ,然后根据是否成功注册来返回有用的信息。在移动端应用的场景下,一般都会有个使用 JSON 格式返回的 API 专门用于用户注册。而且利用 Laravel 的 artisan 命令来创建用户也很常见,尤其是在项目前期的开发阶段。

这些重复代码的确看上去没什么毛病,但如果就这样让业务逻辑继续扩充下去,例如:你希望给新注册用户发一封提醒邮件,那么你需要在两个 Controller 里同时加上这个逻辑。所以为了代码能够整洁优雅起来,我们需要把它放到其他地方去。

别把服务类神化了

一开始,可以先临时创建一个类,把所有针对某个业务逻辑的代码整理起来,例如:

这样看上去就好多了;我们可以直接从 Controller 里调用 User 的 create()/ delete() ,通过任意途径返回结果。然而这方式有个问题:我们处理业务逻辑的时候很少使用一种模式

例如我们有这个业务逻辑:当用户创建账号的时候,需要创建一篇新的博客。如果我们继续使用刚才的模式,我们需要创建 BlogService 然后使其作为依赖注入到 UserService 中:

很显然,当我们的应用变得越来越大,会有越来越多的服务类,有些还依赖了 5 个甚至 6 个其他的服务类,代码像被猫玩过的毛线球剪不断理还乱 —— 我们无论如何都要避免这种结局。

引入动作类

如果我们不使用里面塞了几个方法的单一服务类,而是把逻辑切分到好几个类里面呢?我在最近的好几个工程里都使用了这种模式,结果十分不错。

首先,我们暂时先把“服务”这个抽象而且模糊的术语丢掉,然后引入“动作”类,并且赋予以下定义:

  • 一个动作类,它的名字应该要能够让人一眼看出它是干什么的,例如:CreateOrder, ConfirmCheckout, DeleteProduct, AddProductToCart,…..
  • 动作类应该只拥有一个公开接口(API)。理想情况下应该用统一的名字,例如 handle(), execute() 这样,方便我们需要在动作上实现一些模式,例如适配器。
  • 动作类不应该关心 RequestResponse。动作类不处理任何 Request,也不产生任何 Response,因为这是 Controller 的责任。
  • 动作类可以依赖其他的动作。
  • 当处理业务逻辑的时候,遇到不能返回期望内容的情况下,必须抛出异常,让调用者(或者 Laravel 的 ExceptionHandler)去负责渲染或者返回错误信息。

创建 CreateUser 动作

现在,让我们用 CreateUser 动作类来重构刚才的例子。

你可能觉得奇怪,为什么代码要在 Email 存在的情况下抛出异常,这个不应该是在请求的时候就应该验证一次吗?当然应该让验证器去做这件事,不过,强制动作类遵循业务逻辑是一个好主意。这样做不但能够让业务逻辑更明确(不用过多考虑异常情况),也更容易调试。

下面是我们使用动作类重构后的控制器:

现在我们再也不用在注册流程更改的时候同时修改两边的代码,干净整洁。

内嵌动作

当我们需要往应用里倒入 1000 个用户的时候,我们可以写一个依赖 CreateUser 的动作类:

干净整洁,简单易懂。在这里我们在 Collection::map() 里重用了 CreateUser ,返回一个集合,装着新鲜出炉的用户们。我们可以稍作优化:当有重复邮件的时候,我们可以通过返回 Null 对象,或者往 Logger 里丢 INFO ,随你喜欢。

装饰一下 Actions

现在,假设我们要往日志里记录每一个用户的注册。我们可以直接把代码放到动作自身上,但我们也可以使用装饰器模式

然后,使用 Laravel 的 IoC 容器,我们可以把 LogCreateUser 类绑在 Createuser 类上,这样当我们就总能在需要后者的时候把原型注入进去:

这样做甚至更容易地通过环境变量或者配置控制日志的开启和关闭:

总结

使用这种模式大概会在开始先产生一大堆的类。当然,为了减少一下文章篇幅读起来比较方便,这里只举了这个简单的用户注册例子。当复杂度增加的时候,这个模式的价值就开始体现出来了 — — 因为你清楚这些边界清楚分工明确的代码在哪。

使用动作类的好处:

  • 使用小物件管理领域逻辑可避免重复代码以及增加可重用性,Keep things SOLID。
  • 易于针对不同的场景做独立测试。
  • 清晰易懂、有意义的命名让理解项目变得更加容易
  • 跨项目一致性:避免代码七零八落地分布在 ControllerModels 等等……

而且,这些实践都是基于我最近这些年使用 Laravel 的经验以及我写过的项目总结出来的。它们对我来说非常管用,以至于我甚至在中小型项目里使用。

我非常希望能够与你一同交流分享,如果你有不同的实践方式那就最好不过了!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容