可扩展的 Laravel 应用架构。

dev-main 2024-03-28 12:36 UTC

This package is auto-updated.

Last update: 2024-08-28 13:26:56 UTC


README

目录

关于 Lego

Lego 是一个用于构建可扩展 Laravel 项目的软件架构。它核心融合了 命令总线领域驱动设计,在此基础上构建了一系列目录和类来组织业务逻辑。它还从 SOA(面向服务架构) 中汲取了将功能封装在服务中的理念,并在此基础上丰富了这个概念,而不仅仅是将其作为一个类。

使用 Lego 的目的:

  • 轻松编写干净代码
  • 保护代码免于随时间恶化
  • 在几倍于通常所需的时间内审查代码
  • 将经过验证的最佳实践和模式纳入您的应用程序
  • 在代码和代码库之间导航而不会感到疏远

概念

该架构融合了最佳实践、设计模式和经过验证的方法。

  • 命令总线:用于调度工作单元。在 Lego 术语中,这些单元将是 功能作业操作
  • 领域驱动设计:通过根据它们所属的主题对工作单元进行分类来组织工作单元。
  • 面向服务架构:封装并管理具有相同目的的功能及其所需资源(路由、控制器、视图、数据库迁移等)。

目录

位置

在典型的 MVC 应用程序中,Lego 将是应用程序入口点和执行工作的工作单元之间的纽带,确保代码不会向极端的方向发展。

Lego MVC Position

技术栈

简要介绍...

Lego Stack

框架

提供“内核”以执行繁琐任务的重任,例如请求/响应生命周期、依赖注入和其他核心功能。

基础

扩展框架以提供针对应用程序的特定高级抽象,并且可以在整个技术栈中共享,而不是针对特定情况。

以下是一些可能包含在基础中的示例:

  • DateTime,这是一个用于常见日期和时间函数的支持类
  • JsonSerializableInterface,用于标识可从 JSON 格式序列化和反序列化的对象

提供分离以对属于同一主题的工作和相应的类进行分类。一个领域独立于其他领域运行,并通过 Lego 作业仅向功能和操作公开其功能。

以下是一个示例,说明一个领域可能看起来像什么

app/Domains/GitHub
├── GitHubClient
├── Jobs
│   ├── FetchGitHubRepoInfoJob
│   └── LoginWithGitHubJob
├── Exceptions
│   ├── InvalidTokenException
│   └── RepositoryNotFoundException
└── Tests
    └── GitHubClientTest
    └── Jobs
        ├── FetchGitHubReposJobTest
        └── LoginWithGitHubJobTest

文档 包含更多关于与域一起工作的详细信息。

服务

目录是功能丰富的,用于将[单体应用]({{<ref "/micro-vs-monolith/#monolith">}})分割成多用途应用中的焦点区域。

考虑一个示例应用,我们在这里输入食谱,并希望我们的会员在论坛中进行讨论,我们会有两个服务:1) 厨房,2) 论坛,其中厨房负责所有与食谱相关的内容,论坛显而易见。

app/Services
├── Forum
└── Kitchen

以下是一个单一服务的结构,突出显示了乐高特定的目录

app/Services/Forum
├── Console
│   └── Commands
├── Features
├── Operations
├── Http
│   ├── Controllers
│   └── Middleware
├── Providers
│   ├── KitchenServiceProvider
│   ├── BroadcastServiceProvider
│   └── RouteServiceProvider
├── Tests
│   └── Features
│   └── Operations
├── database
│   ├── factories
│   ├── migrations
│   └── seeds
├── resources
│   ├── lang
│   └── views
└── routes
    ├── api
    ├── channels
    ├── console
    └── web

文档提供了更多关于服务和它们内容的示例。

特性

在类中表示人类可读的应用功能。它包含实现该功能的逻辑,但尽可能简单,通过运行应用程序或服务级别的领域和操作作业。

在控制器的方法(在MVC中)中,将只服务特征类,从而实现最薄的控制器形式。

class AddRecipeFeature extends Feature
{
    public function handle(AddRecipe $request)
    {
        $price = $this->run(CalculateRecipePriceOperation::class, [
            'ingredients' => $request->input('ingredients'),
        ]);

        $this->run(SaveRecipeJob::class, [
            'price' => $price,
            'user' => Auth::user(),
            'title' => $request->input('title'),
            'ingredients' => $request->input('ingredients'),
            'instructions' => $request->input('instructions'),
        ]);

        return $this->run(RedirectBackJob::class);
    }
}

文档介绍了如何将它们作为类从任何地方提供服务。

操作

它们的目的是通过将作业组合在一起,提供跨领域的复合功能来增加代码的可重用程度。

class NotifySubscribersOperation extends Operation
{
    private int $authorId;

    public function __construct(int $authorId)
    {
        $this->authorId = $authorId;
    }

    /**
     * Sends notifications to subscribers.
     *
     * @return int Number of notification jobs enqueued.
     */
    public function handle(): int
    {
        $author = $this->run(GetAuthorByIDJob::class, [
            'id' => $this->authorId,
        ]);

        do {

            $result = $this->run(PaginateSubscribersJob::class, [
                'authorId' => $this->authorId,
            ]);

            if ($result->subscribers->isNotEmpty()) {
                // it's a queueable job so it will be enqueued, no waiting time
                $this->run(SendNotificationJob::class, [
                    'from' => $author,
                    'to' => $result->subscribers,
                    'notification' => 'article.published',
                ]);
            }

        } while ($result->hasMorePages());

        return $result->total;
    }
}

文档介绍了这个简单但强大的概念。

数据

对于一组可扩展的相互关联的数据元素,我们在app/Data中为它们留了一块地方,因为随着时间的推移,在应用程序中编写代码可能会发展出对模型以外的需求,例如仓储、值对象、集合等。

app/Data
├── Models
├── Values
├── Collections
└── Repositories

优势

这看似过度工程化的做法有其宝贵的优势。

组织

  • 在审查代码时,系统变化的可预测影响
  • 由于我们将应用程序划分为独立的焦点区域(分而治之),因此调试时间减少
  • 在单体应用中,我们的每个服务都可以有自己的版本控制系统(例如,API服务是v1,而聊天是v2.3,但它们位于同一个代码库中)

重用与替换

通过将应用程序分解成小的代码构建块,即所谓的单元,我们立即打开了在应用程序中跨数据和方法共享代码以及可替换性的大门,而摩擦和债务最小化。

边界

通过设置边界,你已朝着保护应用程序代码免于变得无法忍受的大而迈出一步,并使新开发者更容易上手。最重要的是,你已经将技术债务降到最低,这样你就不必为错误和失眠买单;代码不是在良好的意图或愿望上运行的。

多租户

当我们的应用程序扩展时,我们通常会有一堆应用程序实例在不同的位置运行,在某个时候,我们会在某些区域激活代码库的某些部分,并关闭其他部分。

以下是一个运行同一应用程序的Api后台办公室Web应用程序实例的简单示例,在乐高术语中,这些是服务,它们通过数据领域共享功能

Lego multitenancy

贡献

错误与问题报告

为了鼓励积极合作,乐高强烈鼓励通过拉取请求进行贡献。“错误报告”可以在问题中搜索或创建,或以包含失败测试或复现错误步骤的拉取请求的形式发送。

如果你提交错误报告,你的问题应包含标题和关于问题的清晰描述。你还应包括尽可能多的相关信息和演示问题的代码示例。错误报告的目的是让你和其他人都能轻松地复制错误并开发修复方案。

⏱ 项目请求(PRs)和问题通常每周检查约三次,因此您的请求很快就会被选中。

Lego Architecture的源代码位于GitHub上,地址为legoravel/lego

支持问题

Lego Architecture的GitHub问题跟踪器不旨在提供帮助或支持。相反,请使用以下渠道之一

  • 讨论区是大多数对话发生的地方
  • 要进行聊天,请访问我们的官方Slack工作区中的#support频道
  • 如果您喜欢在StackOverflow上提问,可以使用#legoravel来标记它们

核心开发讨论

您可以在Lego Discussions中提出新的功能或现有Lego Architecture行为的改进。如果您提出一个新功能,请愿意实现至少一些完成该功能所需的代码,或者在此期间进行活跃的构思合作。

关于错误、新功能和现有功能的实现的非正式讨论发生在Lego Slack工作区#internals频道中。Aboozar Ghaffari,Lego的维护者,通常在每周工作日的早上8点至下午5点(东欧夏令时)出现在该频道,其他时间偶尔出现。

哪个分支?以及如何贡献

main分支包含最新的实时版本,是发布版本。

  • 分叉此存储库
  • 将分叉的存储库克隆到您将编辑代码的位置
  • 为您的编辑创建一个分支(例如 feature/queueable-unitsfix/issue-31
  • 使用有意义的简短消息提交您的更改及其测试(如果适用)
  • 推送您的分支 git push origin feature/queueable-units
  • main分支打开一个PR,这将运行您的编辑测试

⏱ 项目请求(PRs)和问题通常每周检查约三次。

开发设置

以下是设置Lego开发步骤

假设我们位于~/dev目录...

  • 克隆分叉的存储库[your username]/lego,这将创建一个位于~/dev/legolego文件夹
  • 创建一个Laravel项目来测试其中的实现composer create-project laravel/laravel myproject
  • 将创建的Laravel项目连接到本地Lego安装;在Laravel项目的composer.json
    "require": {
        "...": "",
        "legoravel/lego": "@dev"
    },
    "repositories": [
        {
            "type": "path",
            "url": "~/dev/lego",
            "options": {
                "symlink": true
            }
        }
    ],
    "minimum-stability": "dev",

确保您将url更改为您目录的绝对路径

  • 运行composer update以创建符号链接

现在,您在lego目录中的所有更改将自动在项目中生效。

安全漏洞

如果您在Lego中发现安全漏洞,请向Aboozar Ghaffari发送电子邮件至aboozar.ghf@gmail.com。所有安全漏洞都将得到及时处理。

编码风格

Lego Architecture遵循PSR-2编码规范和PSR-4自动加载规范。

PHPDoc

以下是有效的Lego Architecture文档块的示例。请注意,@param属性后面跟着两个空格,然后是参数类型,再跟两个空格,最后是变量名

/**
 * Register a binding with the container.
 *
 * @param  string|array  $abstract
 * @param  \Closure|string|null  $concrete
 * @param  bool  $shared
 * @return void
 *
 * @throws RuntimeException
 */
public function bind($abstract, $concrete = null, $shared = false)
{
    //
}

行为准则

Lego Architecture的行为准则源自Laravel的行为准则。任何违反行为准则的行为都可以报告给Aboozar Ghaffari(aboozar.ghf@gmail.com

  • 参与者应对不同的观点持包容态度。
  • 参与者必须确保他们的语言和行为不包含个人攻击和诋毁性言论。
  • 在解读他人的言语和行为时,参与者应始终假定他人有良好意图。
  • 可以合理认定为骚扰的行为将不会受到容忍。