***这篇文章是与@McrLaravel聚会中的演讲相结合而写的。 相关幻灯片可在此处找到。 ***
在过去的四年中,我一直在与@nowmusic合作开发一个流媒体应用程序,以补充他们不断取得成功的编译CD产品。 在过去的六个月中,我们对技术堆栈进行了一些更改,在本文中,我将探讨一些必要的原因以及Laravel融入故事的几种方式。
作为一个小上下文:现在可以在iTunes,Google Play和Amazon商店中找到音乐。 它具有您希望从现代流媒体平台获得的大多数功能,但由于价格较低而有一些例外。 它包含绝大部分的Now back目录以及定期更新的Charts功能,动态的收音机播放列表生成器以及保存曲目以供离线播放的功能。 整个平台是从地面构建的。 因此使用了ono白标流媒体解决方案-我们自己与内容提供商进行所有集成。

至于技术,我们目前在前端(Swift和Java)上是本机的,在后端主要使用PHP,尤其是Laravel。 我们还托管在处理所有扩展的AWS上。 我们还依赖于他们的相当多的其他服务,例如SQS,ElasticTranscoder,S3等,以提供其他功能。
所以我提到我们目前正在经历一些变化。 在我确切了解这些更改之前,我想先说明一下为什么需要这些更改……
最初构建平台时,它被设想为MVP,以测试市场契合度。 我们快速构建以进行验证,并且通过更改该平台的运行方式(有时是彻底的)对验证和无效做出了反应。 所有这些更改和迭代都是在时间紧迫的情况下进行的。 随着时间的推移,技术债务增加了,而且没有像优秀的开发人员建议的那样频繁地解决。 随着时间的流逝,代码库发展成为只能描述为丑陋的整体。 单个Laravel代码库充满了交叉依赖,“临时” hack和遗留代码。

但重要的是要注意,我们拥有的,直到今天仍在使用的东西仍然有效。 它的核心功能几乎没有问题,除了数据库CPU偶尔出现峰值(我们尚未设法追踪)之外。 那为什么要让自己陷入改变任何事情的麻烦呢? 开发人员的速度降低了,反馈表明这仅仅是因为他们害怕破坏事物。 该项目从一个重要的测试套件开始,由于固定的截止日期和架构变更,这些测试套件随着时间的推移而被废弃,这导致它们需要大量的重构。 功能开发不再有趣,这是一个问题,因为现在开始内部化其开发工作,而不是依赖第三方来获取开发资源。
打破骆驼背的稻草是将亚马逊整合到应用程序购买中的无害任务。 实现此目标的最快,最安全的方法是使用预构建的程序包,这些程序包可以验证购买的商品,一旦用户订阅了设备,这些商品就会发送到后端进行检查。 这正是我们当时处理iOS和Android订阅的方式。 但是,在尝试“要求作曲家要求”适当的程序包时,我们遇到了不幸的情况:这些程序包需要Guzzle的现代版本(一个HTTP请求库),但是我们老化的框架Laravel 4.2却需要一个旧版本。 对于不熟悉PHP依赖关系管理的人来说,这是一件坏事–无法同时要求同一软件包的两个版本。 为了集成我们轻松实现Amazon IAP所需的软件包,我们必须将其更新为Laravel 5 —整个代码库重构。
最终,我们设法解决了问题,但最终造成了损失-衣衫不整的巨石失去了信任,我们开始寻找新的想法。

Boromir有观点,显然已经在阅读@spolsky的著作,但是必须根据我们认为的两个选择来做出决定:
- 雇用一个新的开发团队,并教给他们有关旧平台的所有知识,该旧平台四处折腾,立即会阻碍他们快速迭代的能力。
- 雇用一个新的开发团队,并在以前的开发人员的指导下替换旧的平台,让他们承担任务,他们已从旧平台的开发中学到了很多经验。
在与管理层进行了有关利弊的讨论后,为了将来的开发团队,我们决定选择选项2。
因此,我的任务是提出比以前更好的东西,并且像任何知名的开发人员一样,他们知道足够危险,所以我决定最好的选择是将一些Docker和Kubernetes混合使用。

总体思路是将应用程序的业务逻辑记录和整理为一组命令和查询,其概念类似于CQRS模式,该模式在.NET领域非常流行,但实际复杂度仅为10%。 命令和查询(累计:动作)将被创建和迭代,直到它们以尽可能简单的方式覆盖业务域的所有必需功能为止。 然后将创建此核心服务以处理这些命令和查询-它们还将通过命令总线,该总线将能够在每次执行Action之前和之后都应用交叉关注点。 仅允许Core与数据库进行对话,以确保只有经过同意,验证和测试的业务交互集才能够更改平台内的任何状态。
通过采用这种方法,我们将建立业务的原始表示,而无需担心外部世界如何与之交互。 处理用户交互的工作将由其他服务(网站,API等)负责。我想您可以将面向用户的服务视为MVC的V和C,这将使Core成为模型,但在多个前端之间共享并受到保护通过非常严格,经过良好测试的界面。
该计划看起来不错,所以我们开始构建,构建的越多,感觉就越好。 核心服务很快就到位了。 严格的CQRS界面与更新的开发流程相结合,始终可以保证接近100%的测试覆盖率。
随着Now的内部开发人员加入前瞻性计划,GitLab,同行评审,全面的CI设置以及使用docker封装服务本身和我们的开发环境的结合,这使我们能够以非常小的团队保持快速的输出。
但是与此帖子相关的话题将在Laravel聚会上发表,到目前为止,我所做的一切都是关于技术债务的bit昧,并展示了一些新颖的架构。 因此,我最好谈论一些Laravel特有的事情,对此我们感到很惊讶。
认证问题
通过将我们的平台分为多种服务(每个独立的Laravel代码库),我们需要找到一种方法,允许用户通过任何面向用户的身份验证,尤其是网站和API。 我们还有其他一些要求:
- 服务责任应明确界定和区分
- 只有Core应该对数据库进行任何更改
- 我们想使用Laravel的内置身份验证抽象,以使新开发人员可以轻松加入并确保与其他Laravel库的兼容性。
对我们来说幸运的是,事实证明Laravel基于会话的身份验证(我们希望在网站中使用)使用名为UserProvider的接口,该接口负责为用户对象交换用户凭据。 该接口的标准实现是DatabaseUserProvider,它仅从数据库中提取一行并生成用户有说服力的模型。 该实现通过IoC容器解决,因此替换它也非常容易:我们所需要做的就是创建自己的实现,该实现将用户的凭据发送到Core,然后将响应解析为User对象。 准备好此替代实现后,可以将其替换为默认实现。 简单。
我们也想将Laravel Passport用于我们的API,因为大多数Laravel开发人员都熟悉它。 就像会话身份验证一样,它使用UserProvider接口,我们已经在网站代码库中替换了该接口。 将其复制并粘贴到API即可使用。 不过,我们确实遇到了一个障碍……Passport进行了硬编码,假设它可以在数据库中存储oAuth令牌,但是我们不想仅为oAuth令牌提供新的数据库,而不是当我们已经有一个Redis集群时用于存储网站会话令牌。 为了覆盖此功能,我们必须遵循类依赖树大约6个级别,以找到IoC容器正在解析的第一个类。 然后,我们能够对这六个类中的每一个进行子类化,迫使它们在依赖关系树下引用我们的子类,直到最终我们能够注入oAuth令牌存储的自定义实现。
我想Taylor并没有为界面建立所有东西 。
环境特定服务
我们需要在多种环境中运行我们的平台:开发,配置项,登台,生产以及可能的其他环境。 在上述每种环境中,我们都需要能够对我们的代码库的功能进行调整,尤其是在与第三方API通信时。 条纹就是一个很好的例子。 在本地开发人员和暂存中,我们希望能够联系Stripe的测试环境。 在CI中,我们希望将其完全存根,以便我们的测试超级快速地运行。 在生产中,我们显然想与Stripe的生产服务器进行对话。 这种所需的灵活性还扩展到内部功能,例如,我们不希望登台环境每周发送财务报告给我们的合作伙伴!
我们通常要建议的第一步是通常的做法:构建接口。 通过围绕内部和外部组件创建接口包装器,我们能够创建遵守该接口的多个不同实现,并将它们适当地放入我们的代码库中。 例如,我们已将平台的机密管理组件构建为具有两个具体实现的接口:从平面文件加载机密和从AWS Secrets Manager加载机密。 如果出于某种原因我们要迁移到Google Cloud,那么交换我们管理机密的方式将是微不足道的-只需创建该接口的新实现即可。
我们还大量使用Laravel的IoC容器,以便将所有接口注入需要它们的类中。 这从这些依赖类中删除了关于它们实际加载哪种实现的所有知识。 它还为我们提供了一个中心位置(此IoC容器),以在我们不同的接口实现之间进行交换。
使用此设置,剩下的唯一一件事就是将实际使用的实现告知IoC容器。 我们的解决方案是使用ENV变量将此信息放置在代码库的环境中。 这使我们能够再次将与要使用的具体接口实现相关的任何逻辑与代码库分离,并将其置于基础结构级别,这是我们最初的目标。 不同的运行时环境中的不同功能。 实际上,我们可以在运行时在docker容器(我们用于所有服务的记忆)中定义ENV变量,这样我们就可以琐碎地旋转具有不同功能的同一代码库的不同副本。
实际上,只要调整了ENV配置,Kubernetes就会通过自动重新配置正在运行的容器来使这一步更进一步。 这在扩展环境中非常有用,因为它允许我们在具有多个运行容器的实时服务中交换代码实现,而不必担心单独调整它们。
所有这些灵活性使我们可以选择做一些很酷的事情:
- 将新代码推送到界面后的生产环境中,并通过小的配置更改在整个集群中激活它。
- 通过告诉Kubernetes逐步推出ENV更改,逐步在接口后面推出代码。
- 创建一个与生产环境相同的暂存环境,但ENV变量除外,并且在与第三方进行交互时仍能够运行截然不同的代码。
- 在CI测试期间以及在本地开发人员期间,不进行与所有外部服务的交互。
总体而言,开发人员的经验提高了十倍,并且到目前为止,生产率仍然很高。 到目前为止,我们已经完成了三项新的dockerized服务,我想时间会告诉我们是否选择正确的路径,方法是选择重新构建,然后将K8引入混合物中。