PHP 框架中的“单体应用”回归:在 AI 面前,我们终于不再假装自己是分布式系统
大家好,欢迎来到今天的架构讲座。
坐在这里,看着台下这么多对“云原生”、“微服务”、“服务网格”趋之若鹜的同学们,我感到一种深深的……疲惫。这种疲惫不是来自写代码,而是来自听你们解释为什么要把一个简单的“用户登录”功能拆成五个微服务,还要配一个 Kafka 做消息队列,最后发现因为网络延迟,用户提交表单要转三圈圈才能看到“成功”的提示。
各位,我们要聊聊那个被我们抛弃了十年的“老朋友”——单体应用。
是的,你没听错。在这个所有人都忙着给大象做剖腹产(微服务化)的时代,我们决定要把大象装回冰箱里。为什么?因为手里多了一把锤子(AI),我们突然发现,造那个锤子(分布式架构)的成本,比把大象装进去(单体应用)还要高。
今天,我们就来聊聊,为什么在 AI 辅助开发的今天,单体应用才是“可维护性”的终极奥义。
第一部分:微服务的“皇帝新衣”与“屎山”的诱惑
让我们先诚实地面对一下过去十年的架构演变史。那基本上就是一部“为了复杂而复杂”的历史。
十年前,我们写代码,就像在搭乐高。代码是模块化的,逻辑是清晰的。那时候,如果要把一个功能加进去,我们在 app/Services/UserService.php 里加几行,完美。
后来,互联网公司开始变大,业务变多,传统的单体应用像一个发福的中年人,跑不动了。于是,架构师们出现了。他们穿着印有“High Availability”(高可用)和“Eventual Consistency”(最终一致性)的连体裤,在白板上画出了蜘蛛网一样的架构图。
“我们要解耦!”他们说。
“我们要横向扩展!”他们说。
“我们要独立部署!”他们说。
结果呢?结果是你们现在的代码库。你们的服务之间通过 RESTful API 通信,或者更糟糕,通过 gRPC。你们的接口文档比圣经还厚。你们的中间件比面条还乱。
你们有没有试过调试一个跨越了三个服务、经过了两个消息队列、最后在第四个服务里抛出异常的请求?
你打开日志,上面写着:Error in Service B: Timeout waiting for Service A。
你:???
这时候,你那个写微服务的架构师同事会淡定地喝一口咖啡,说:“这是分布式系统的常态,这是 CAP 定理的代价。”
别信他。这就是个借口。这就是给“没写好代码”找的漂亮包装纸。为了解决一个简单的业务逻辑,你们建立了庞大的基础设施。服务器多了,运维难了,版本兼容性崩了,数据一致性崩了。你们就像是在一个精密的瑞士钟表里塞了一团棉花,原本的机械美感荡然无存。
这时候,AI 出现了。AI,特别是像 GitHub Copilot 或者 Cursor 这样的代码助手,它们不是在“理解”你们的代码,它们是在“阅读”你们的代码。
当你的代码被切得七零八落,分散在 50 个不同的仓库里时,AI 怎么写代码?它得先读仓库 A,生成仓库 A 的代码,然后读仓库 B,再写仓库 B。它的工作流就像是在接力赛里捡豆子。
而在单体应用里?AI 就像是一个拥有上帝视角的实习生。它打开你的整个项目文件夹,一眼就能看到整个业务逻辑的全貌。
第二部分:AI 的语境窗口与“上帝视角”
想象一下,你有一个复杂的电商下单流程。
在微服务架构里,这个流程是这样的:
- 用户点击“购买” -> 前端调用
OrderService。 OrderService调用InventoryService检查库存。InventoryService调用Database,返回结果。OrderService检查UserCreditService的积分。UserCreditService调用Database。OrderService计算总价。OrderService调用PaymentService扣款。PaymentService调用第三方支付网关。
这一路上,网络请求、序列化、反序列化、重试机制、熔断机制。有多少代码是在处理这些网络杂音的?也许 20% 是业务逻辑,80% 是胶水代码。
而在单体应用里,这一切都是本地调用。
// app/Services/OrderService.php
class OrderService
{
public function checkout(User $user, Cart $cart): Order
{
// 1. 检查库存 - 直接调用本地方法
$inventory = $this->inventoryRepo->check($cart->items);
if (!$inventory->isAvailable()) {
throw new InsufficientStockException();
}
// 2. 扣减库存 - 事务保护,原子性保证
DB::transaction(function () use ($cart, $inventory) {
$inventory->deduct($cart->items);
$this->createOrder($cart);
});
// 3. 扣款 - 同一个事务,数据一致性最强
$payment = $this->paymentGateway->charge($user->paymentMethod, $cart->total);
return $payment->order;
}
}
看到了吗?多干净。多直接。
现在,让我们看看 AI 在这种情况下是怎么工作的。
场景: 你想优化这个下单流程,增加一个“优惠券系统”。优惠券规则很复杂,涉及满减、折扣、会员特权,逻辑有 500 行。
在单体应用中:
你把这段逻辑放在 app/Services/CouponService.php 里。你把文件拖进 IDE,让 AI 读一下这个文件。AI 拿到了完整的上下文:优惠券怎么验证、怎么计算折扣、怎么判断用户资格。AI 生成的代码是一气呵成的,逻辑顺畅,符合现有架构。
在微服务中:
你想加个优惠券服务。你得新建一个项目,配置 docker-compose.yml,写 composer.json,定义 gRPC 接口 coupon.proto,写服务端代码,写客户端 Stub。等你把所有配置都搞定了,你才终于可以开始写那 500 行优惠券逻辑。
然后,你发现因为网络延迟,用户下单时,优惠券已经过期了。你还得去写超时重试逻辑,或者引入 Saga 模式。
AI 是来帮我们写代码的,不是来帮我们写“分布式事务补偿逻辑”的。在单体应用里,AI 的代码生成能力被无限放大,因为它的上下文是完整的。
第三部分:可维护性的真相——调试与阅读
我常说,软件工程里最难的不是写代码,而是“读别人的代码”以及“找别人的 Bug”。
当你的应用是一个单体时,Bug 是诚实的。它在某一个文件里尖叫。
// app/Models/User.php
class User
{
public $id;
public $name;
// 这是一个著名的 Bug,把 'name' 拼写成了 'nmae'
public function getNmaeAttribute(): string
{
return $this->name;
}
}
你打开 Chrome DevTools,看堆栈跟踪。Stack trace: /var/www/app/Models/User.php:42。你切到 42 行,找到那个拼写错误。改掉,提交,部署。
简单粗暴,有效。
现在,假设你的应用是微服务架构的。
同样的 Bug,出现在了 UserService 微服务里。你在日志里看到了报错。
[ERROR] UserService: Undefined property: $nmae
你问 DevOps:“日志在哪?”
DevOps:“在 K8s 的 pod 里?哦,那个 pod 早上三点重启了,日志已经 Flush 到 Loki 了,你在那找吧,大海捞针。”
你问前端:“能不能帮我看看?”
前端:“我的代码只是调用了 getUser() 接口,数据是从 UserService 返回的,跟你有什么关系?”
你问后端:“你写那个接口的时候没检查拼写吗?”
后端:“我忙着配 Envoy Inbound Filter 呢。”
这就是单体应用的可维护性优势。低延迟 = 低认知负担。
当 AI 辅助开发时,认知负担是一个关键指标。如果你需要花费 30% 的时间去理解系统是如何运转的(网络调用链、配置、中间件),那么你只有 70% 的精力可以用来创造价值。
单体应用把 30% 的精力直接省了下来。AI 也不需要去理解复杂的微服务拓扑结构,它只需要理解你的代码库结构。这对于维护长期项目至关重要。
第四部分:模块化单体
我知道,你们会反驳:“但是单体应用容易‘爆炸’啊!修改一个功能会影响其他功能!”
朋友们,这叫“耦合”,不叫“单体”。
把单体应用和“面条式代码”划等号,是 PHP 社区最大的误解。
我们提倡的回归单体,不是回到那个所有代码都堆在一个 index.php 文件里的时代,而是模块化单体。
这种架构的核心思想是:物理上在一起,逻辑上解耦。
让我们看看一个现代、整洁的 PHP 单体架构长什么样。假设我们使用 Laravel 风格的结构,但在组织上更加严谨。
/app
/Modules
/Payment (支付模块)
/Controllers
/Services
/Models
/Migrations
/Inventory (库存模块)
/Controllers
/Services
/Models
/Migrations
/Core (核心模块)
/Interfaces
/Enums
/Traits
注意到了吗?即使我们在一个物理仓库、一个部署包里,我们依然在用面向对象编程的原则。
在这个架构下,Payment 模块和 Inventory 模块是独立的。Payment 依赖 Core 模块的接口,但不依赖 Inventory 的具体实现。
当 AI 开发新功能时,它非常擅长这种结构化思维。
提示词:
“我正在开发一个新功能,需要处理订单支付。请参考
/app/Modules/Payment的代码风格,使用 Dependency Injection 模式,并调用/app/Modules/Core/Interfaces/PaymentGatewayInterface.php。”
AI 生成的代码:
// app/Modules/Payment/Services/PayPalService.php
namespace AppModulesPaymentServices;
use AppModulesCoreInterfacesPaymentGatewayInterface;
use AppModulesCoreEnumsPaymentStatus;
class PayPalService implements PaymentGatewayInterface
{
private $client;
public function __construct(PayPalClient $client)
{
$this->client = $client;
}
public function charge(float $amount): PaymentStatus
{
// AI 理解了 PayPal 的 API 调用流程
$response = $this->client->executePayment([
'amount' => $amount,
'currency' => 'USD'
]);
return $response->isSuccess() ? PaymentStatus::SUCCESS : PaymentStatus::FAILED;
}
}
看,AI 生成的代码结构清晰,依赖明确。即使所有这些都在同一个服务器上运行,这种代码的可维护性也是极高的。
为什么?因为你可以安全地重构 InventoryService 的内部实现,而不需要重新部署 PaymentService。你只需要确保 InventoryService 实现了它应该实现的接口即可。
这就是微服务梦寐以求的“松耦合”,但它是以更低的成本(不需要网络调用,不需要配置服务注册中心)实现的。
第五部分:实体化单体与 AI 的协同进化
现在,让我们谈谈一个更激进的概念:实体化单体。这是由 Elton M. Santos 提出的一个非常有趣的观点。
他的意思是:微服务是一种“基于协议”的架构,你定义了 API,然后实现它。而单体应用是一种“基于实体”的架构。
在单体应用中,你的代码是基于现实世界事物的,而不是基于协议。
举个例子,我们要开发一个“库存系统”。
在微服务思维下,你定义了 InventoryService,暴露了 GetStock 和 UpdateStock 的 API。然后你开始写代码。
在单体思维下,你直接定义一个 InventoryItem 实体。
// app/Entities/InventoryItem.php
namespace AppEntities;
class InventoryItem
{
protected string $sku;
protected int $quantity;
protected float $price;
public function __construct(string $sku, int $quantity, float $price)
{
$this->sku = $sku;
$this->quantity = $quantity;
$this->price = $price;
}
public function decrease(int $amount): void
{
if ($this->quantity < $amount) {
throw new OutOfBoundsException('Not enough stock');
}
$this->quantity -= $amount;
}
// ... getters ...
}
AI 在这里大放异彩。
当你开始写 InventoryItem 实体时,AI 会自动补全方法,甚至会提示你添加验证逻辑。
更妙的是,AI 可以帮你处理这个实体的生命周期。比如,当库存不足时,AI 可以自动生成一条消息发送给管理员。
// app/Listeners/StockLowListener.php
namespace AppListeners;
use AppEntitiesInventoryItem;
use AppServicesNotificationService;
class StockLowListener
{
public function __construct(
private NotificationService $notificationService
) {}
public function handle(InventoryItem $item): void
{
if ($item->getQuantity() < 10) {
$this->notificationService->notifyAdmin(
"Stock Alert: {$item->getSku()} is running low!"
);
}
}
}
在单体应用中,这种实体和行为的紧密绑定,让代码读起来就像是业务规则的自然描述。你不需要去阅读一堆 JSON 定义,也不需要去解析 Protobuf 的字段。
AI 非常擅长这种自然语言的代码生成。 它能理解“当库存低于 10 时发送通知”这个逻辑,并把它转化为直接的代码逻辑。
而在微服务架构中,为了实现这个逻辑,你可能需要:
- 监听
InventoryService的 Kafka 消息。 - 写一个消费者函数解析消息。
- 调用
NotificationService的 API。 - 处理错误重试。
这中间的每一行代码,都是对真实业务逻辑的干扰。AI 生成的代码越多,这种干扰就越大,Bug 就越多。
第六部分:性能与部署——被遗忘的基石
别以为单体应用就是“慢”。那是十年前的单体应用。
现代单体应用可以跑得飞快。PHP 就是为此而生的。它的高性能、低内存占用,在单体应用中简直是如鱼得水。
想象一下,你的系统有 10,000 个请求每秒。在单体应用中,你只需要扩容 PHP-FPM 进程。这是垂直扩展,是 CPU 和内存的极致利用。
在微服务架构中,你把 10,000 个请求分成了 10 个服务。你需要在每个服务里复制你的数据库连接池逻辑,复制你的缓存逻辑,复制你的队列监听逻辑。
单体应用中的 AI 开发者,可以非常轻松地利用这些优化。比如:
// app/Services/CacheService.php
class CacheService
{
private Redis $redis;
// AI 会自动帮你管理这个 Redis 实例的生命周期
public function __construct(Redis $redis)
{
$this->redis = $redis;
}
public function get(string $key): ?string
{
$value = $this->redis->get($key);
// AI 可以在这里建议你添加序列化逻辑
return $value ? unserialize($value) : null;
}
}
部署方面,单体应用的优势更是统治级的。
在单体应用中,部署就是一个命令:
git pull origin main && composer install && php artisan migrate && php artisan queue:work
在微服务架构中,部署是一个噩梦。你有一个 API 服务,一个 Worker 服务,一个 Search 服务。你需要编写 Dockerfile,构建镜像,推送到镜像仓库,配置 Kubernetes 的滚动更新策略,等待健康检查通过。
当你有一个 AI 助手帮你写代码时,你希望 AI 专注于业务逻辑,而不是帮你写 Dockerfile 或者 CI/CD Pipeline。
把单体应用部署简单化,意味着你的 AI 助手有更多的时间去优化你的业务代码。
第七部分:当单体应用真的“爆炸”时
最后,我们来谈谈恐惧。所有的架构师都怕单体应用。他们怕“牵一发而动全身”。
但是,大家有没有想过,为什么单体应用会“爆炸”?
通常是因为代码写得太烂。不是因为它是一个单体应用。
如果一个单体应用结构清晰,模块分明,它其实是极其稳定的。就像一个积木塔,只要积木块之间的接口(接口)定义得好,就算你拆掉一块,其他的积木也不会倒。
这种“模块化单体”,就是单体应用的终极形态。
在这种架构下,AI 的作用不仅仅是写代码,更是重构。
当一个庞大的单体应用因为业务增长变得臃肿时,你不需要把它切成微服务(这通常是个错误的决定)。你可以利用 AI 来帮你进行重构。
你可以告诉 AI:
“把
/app/Services/OldLegacyService.php重构为一个独立的模块,抽取公共逻辑到/app/Shared/目录下,并确保所有调用者都通过依赖注入来使用它。”
AI 会帮你完成代码的拆分。最终,你的单体应用变成了一个“模块化单体”。它在物理上还是在一起的,但逻辑上,它已经进化成了微服务的架构模式。
这时候,你拥有了单体应用的部署简单性,又拥有了微服务的逻辑解耦性。
结语:简单是复杂的终极形式
各位,在这个充满噪音和炒作的技术世界里,保持清醒的头脑是多么重要。
我们往往高估了一个系统的短期价值,而低估了它的长期维护成本。
微服务是一种架构风格,它不是一种编程范式。你不能为了用微服务而用微服务。
而在 AI 辅助开发的今天,单体应用回归了它应有的地位。它回归到了“代码即业务逻辑”的本质。
所以,下次当你想要为了“解耦”而创建一个新的微服务时,先问自己一个问题:
“我能不能用 AI 帮我写一个更清晰的模块,放在同一个仓库里,而不是写一堆 HTTP 接口?”
如果答案是肯定的,那就别折腾了。写一个漂亮的单体应用吧。它更稳、更快、更易维护,而且,它能让你在周五晚上准时下班。
毕竟,我们的目标不是架构图有多漂亮,而是产品能活多久,代码能看懂多久。
谢谢大家!