当前位置: 首页 > 科技观察

大型项目应该如何分层?是时候告别MVC了

时间:2023-03-13 23:44:39 科技观察

最近使用laravel制作了自己的个人博客。过程中也思考了一些问题。如何更优雅地编写我的代码?为什么laravel没有模型目录?那么逻辑代码和数据库查询代码应该怎么放呢?我们一直被灌输的设计思想,即M-V-C、模型(Model)、视图(view)、控制器(Controller),某种程度上是因为RubyonRails的流行。在大部分开发者眼中,Model就是数据库操作之类的东西,但是在实际项目开发过程中,我们会有很多逻辑代码,比如数据校验,调用外部服务,发送邮件等等,所以很多开发者只需将业务逻辑封装到控制器中即可。当控制器足够大时,它们将需要重用其他控制器中的业务逻辑。大多数开发者并没有将这些业务逻辑提取到其他类中,而是错误地认为需要在控制器中调用其他控制器方法。这种模式通常被称为“HMVC”。不幸的是,这种模式通常也意味着糟糕的编程和过于复杂的控制器。HMVC意味着糟糕的设计:你觉得有必要在你的控制器内部调用其他控制器吗?这通常意味着糟糕的编程和控制器中过多的业务逻辑。一个好的做法是提取控制器中的业务逻辑并将其放入新的第三方类中。通常,我们称这个第三方类为服务类,这样你就可以将它注入到所有其他控制器中。服务类并使用它们。有一种更好的方法来构建您的应用程序。但首先我们必须忘记我们所学的关于“模型”的一切。简而言之,让我们删除模型目录并重新开始!再见,您是否删除了模型目录?赶快删掉!我们要在app目录下新建一个目录,目录名称以我们的应用命名,比如QuickBill。我们将继续使用前面章节中编写的那些接口和类。>注意使用场景:记住,如果你正在构建一个小型的Laravel应用程序,在models目录下写几个Eloquent模型其实是相当合适的。但在本章中,我们主要关注如何开发适用于分层架构的大型复杂项目。所以,我们现在有一个app/QuickBill目录,它与应用程序目录中的其他目录(如Http和Console)处于同一级别。在QuickBill目录下,我们还可以创建其他几个目录,例如Repositories和Billing目录。创建目录后,不要忘记通过composer.json文件中的PSR-4自动加载机制将它们注册到QuickBill命名空间中:"autoload":{"classmap":["database/seeds","database/factory"],"psr-4":{"App\\":"app/","QuickBill\\":"app/QuickBill"}},现在,我们把所有的Eloquent模型类放到QuickBill目录下。这样,我们就可以很方便地以QuickBill\User和QuickBill\Payment的形式来使用它们了。Repositories目录包含PaymentRepository、UserRepository等仓库类,其中包含getRecentPayments、getRichestUser等所有对数据的访问函数。Billing目录包含用于调用第三方支付服务(例如Stripe和Balanced)的类和接口。整个目录结构现在应该是这样的://app//QuickBill//Repositories->UserRepository.php->PaymentRepository.php//Billing->BillerInterface.php->StripeBiller.php//Notifications->BillingNotifierInterface.php->SmsBillingNotifier.phpUser.phpPayment.php的数据验证在哪里?在哪里执行数据验证常常让开发人员感到困惑。考虑将数据验证方法写入您的“实体”类(例如User.php和Payment.php)。方法名称可以设置为validForCreation或hasValidDomain。或者你也可以创建一个验证器类UserValidator,把它放在Validation命名空间下,然后把这个验证器类注入到你的Repository类中。您可以尝试两种方法,看看您更喜欢哪一种!当然,在Laravel5.*中,你不需要自己创建验证器类,Laravel自带的验证器类可以满足你的所有需求。通过摆脱models目录,您通常可以克服良好架构设计的心理障碍,并为您的应用程序构建更合适的目录结构。当然,你构建的每一个应用程序都会有一些相似之处,因为无论应用程序多么复杂,它都需要一个数据访问层(Repository),一些外部服务层等等。不要害怕目录:不要害怕创建更多目录来组织您的应用程序。总是需要将整个应用程序分成多个细分的功能组件,每个组件专门负责一定的职责。跳出“模型”的框框思考总是有帮助的。例如,正如我们之前讨论的,您可以创建一个Repositories目录来保存所有数据访问类。核心思想是分层。您可能已经注意到,优化应用程序目录结构的关键是划分不同组件的职责,或者为不同的职责创建不同的层。控制器只负责接收和响应HTTP请求,然后调用相应的业务逻辑层类。您的业??务逻辑/领域逻辑层是应用程序的核心部分,它包含读取数据、验证数据、执行支付、发送电子邮件以及应用程序中所有其他功能的代码。事实上,您的域逻辑层不需要了解“Web”的任何信息!Web层只是一种访问应用程序的传输机制,有关Web和HTTP请求的所有内容都不应该超出路由和控制器层的范围。做好架构设计确实具有挑战性,但是好的架构设计也会带来可维护和更简洁的代码。比如,与其在业务逻辑类中直接获取web请求,不如将web请求通过controller传递给业务逻辑类。这个简单的更改会将您的业务逻辑类与“Web”层分离,您可以轻松地测试您的业务逻辑类,而不必担心模拟Web请求:}publicfunctionpostCharge(Request$request){$this->biller->chargeAccount(Auth::user(),$request->input('amount'));returnview('charge.success');}}现在是chargeAccount方法更容易测试,因为我们不再需要使用BillerInterface实现类中的User和Request类,只需将用户和金额传递到方法中即可。编写可维护应用程序的关键之一是职责分离。始终检查一个类是否变得过于广泛并且知道一些它不应该知道的东西。你必须经常问自己“这个类需要关心X吗?”如果答案是否定的,那你就得把这段逻辑提取到另一个类中,然后通过依赖注入的方式注入进去。您如何判断课程是否超出范围?一个有用的方法是检查为什么要更改代码。比如我们要调整通知逻辑时,是否需要修改Biller的实现代码?当然不是,Biller实现只关注支付,它应该只通过合约与通知逻辑交互。在处理代码时使用这种思维方式可以帮助您快速识别应用程序中需要改进的地方。一切都去哪儿了?在使用Laravel开发应用程序时,您可能会对将各种“东西”放在哪里感到困惑。例如,辅助函数去哪里了?事件监听器去哪里了?视图组件去哪里了?答案可能会让您大吃一惊——“无论什么地方!”Laravel对文件系统的这个约定没有太多要说的。不过,这个答案确实不能令人满意,那我们就来讨论一下这个问题,讨论一下这些“东西”可以放在哪里。辅助函数我们可以在app目录下创建一个helpers.php文件,然后将所有自定义的辅助函数放在这个文件中。当然,由于这个文件不包含类,所以不适合通过命名空间来引用其中的函数。它需要在composer.json中全局注册:"autoload":{..."files":["app/helpers.php"]},然后运行??composerdump-auto重新注册自动加载映射。然后可以在应用程序中使用app/helpers.php中定义的辅助函数。事件监听器当然事件监听器不应该放在routes.php文件中,所以我们需要另找地方存放。其实在Laravel5.*中,当我们通过phpartisanmake:listener命令创建时间监听器时,系统会自动在app目录下创建一个Listeners子目录,并将生成的监听器类放在该目录下。所以我们自定义的事件监听类,放在这个目录下就行了。错误处理器在Laravel5.*版本中,所有的异常默认都由App\Exceptions\Handler处理。您也可以通过自定义异常类来处理它们,这些异常类可以放在app/Exceptions目录中。其他一般情况下,只需遵循PSR-4规范即可使您的类在应用程序目录结构中保持整洁。结合你目前学到的知识,你应该能够对什么代码应该放在哪里的问题给出一个有根据的答案。但永远不要拒绝尝试。Laravel的美妙之处在于您可以制定最适合您自己的应用程序的约定。去探索和发现最适合您自己的应用程序的结构,不要忘记与他人分享您的见解!例如,您可能会受到我们之前示例的启发,并在QuickBill中创建一个Providers目录来存储您所有的自定义服务提供商,或者,目录结构如下所示://app//QuickBill//Billing//Extensions//Pagination->Environment.php//Providers->EventPusherServiceProvider.php//RepositoriesUser.phpPayment.php注意上面的例子,我们有Providers和Extensions两个命名空间。您所有的自定义服务提供者都可以放在Providers命名空间下,而Extensions命名空间可用于存储您扩展框架核心的类。