iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
Software Development

重新認識主幹開發(Trunk-Based Development)系列 第 18 篇

Day 18:依賴注入與新舊實作並存

  • 分享至 

  • xImage
  •  

建立共同介面後,新舊類別可以提供相同的操作,但呼叫端如果仍直接建立某個類別,切換時就得逐一修改。為了讓呼叫端不必隨著實作切換而修改,可以改由外部建立物件,再傳給呼叫端使用,這個做法稱為依賴注入(Dependency Injection)。

以下延續 Nathan 團隊的寄信改版情境。Mandy 已抽出 Sender 介面,原本的 ImmediateSender 仍照常寄信;Brent 則在準備背景寄送需要的環境。

透過建構子注入依賴

以準備客戶通知信的 CustomerNotifier 為例,原本的程式直接建立 ImmediateSender:

namespace App\Services;

use App\Mail\ImmediateSender;
use App\Mail\NotificationMail;

final class CustomerNotifier
{
    public function notify(string $email, string $text): void
    {
        $sender = new ImmediateSender();
        // NotificationMail 是這個案例使用的通知信類別,負責主旨與內容。
        $sender->send($email, new NotificationMail($text));
    }
}

原本的類別關係如下:

https://ithelp.ithome.com.tw/upload/images/20261002/20102562fLutP1cFDn.png

CustomerNotifier 是自己建立出 ImmediateSender 物件,所以即使寫好新的寄信類別,只要 CustomerNotifier 程式不修改,它建立的一樣還是 ImmediateSender 物件。

要讓 CustomerNotifier 換用其他寄信類別時不必修改程式,可以把建立物件的動作移到外部,改由建構子接收 Sender 介面的物件。之後只要傳入另一個符合約定的實作,就能換用不同的寄信方式。

調整後的 app/Services/CustomerNotifier.php 如下:

namespace App\Services;

use App\Mail\NotificationMail;
use App\Mail\Sender;

final class CustomerNotifier
{
    public function __construct(private Sender $sender)
    {
    }

    public function notify(string $email, string $text): void
    {
        $this->sender->send($email, new NotificationMail($text));
    }
}

改用建構子注入後,類別關係如下:

https://ithelp.ithome.com.tw/upload/images/20261002/20102562jzzk0oYiew.png

設定容器綁定

接著以 Laravel 的服務容器設定 Sender 對應的實作,讓容器建立物件並傳入建構子。在既有 AppServiceProvider 匯入對應類別,於 register() 方法加入以下綁定:

use App\Mail\ImmediateSender;
use App\Mail\Sender;

public function register(): void
{
    $this->app->bind(Sender::class, ImmediateSender::class);
}

這份綁定表示,需要 Sender 時就提供 ImmediateSender。

取得通知物件的程式也改由容器建立:

$notifier = app(\App\Services\CustomerNotifier::class);
$notifier->notify($email, $text);

通知功能仍使用原本的寄信方式,之後可以透過容器綁定切換實作。

調整 CustomerNotifier 的建構子時,建立它的程式也要一起改成由容器取得物件。確認這個通知功能仍能正常寄信後,就可以整合到主幹。

其他通知功能可以分批採用相同做法,不必一次全部改完。不過,仍直接建立 ImmediateSender 的功能不會跟著容器綁定切換,因此切換前要完成這些調整。

實作佇列寄送

新增 app/Mail/DomainQueueSender.php,實作 Sender 介面,依收件網域決定寄送方式:

namespace App\Mail;

use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\Mail;

final class DomainQueueSender implements Sender
{
    public function send(string $email, Mailable $message): void
    {
        $domain = strtolower(substr(strrchr($email, '@'), 1));

        if ($domain === 'example.net') {
            Mail::to($email)->queue($message);
            return;
        }

        Mail::to($email)->send($message);
    }
}

符合條件時,改用 queue() 將信件排入佇列;其餘情況仍使用 send() 立即寄送。

類別名稱直接說明差異:ImmediateSender 在當下執行寄送,DomainQueueSender 依網域選擇是否排入佇列。如果只叫做 OldSender、NewSender,接手的人還要先了解這次改版,才知道兩者分別做什麼。

新舊實作的整合與驗證

新增 DomainQueueSender 後,容器仍綁定 ImmediateSender,通知功能繼續使用原本的寄信方式。Mandy 則在測試中直接取得 DomainQueueSender,驗證新的寄信方式。

https://ithelp.ithome.com.tw/upload/images/20261002/20102562wL8ecVZKvU.png

新實作也可以拆成數個提交,但每一步都要能建置、啟動並通過當時必要的驗證。整合時沿用直接提交與短期分支介紹的審查與驗證流程。

新實作雖然還沒正式使用,仍要跟著其他程式的變更一起維護。例如,其他成員修改共用的通知信程式後,原本的寄信方式仍能正常運作,新實作的測試卻可能失敗。因此,修改共用程式時,要一起更新並執行兩種寄信方式的相關測試。新舊實作都在主幹上,團隊就能及早確認這些影響,不必等到準備切換時才處理。

Brent 可以在 Mandy 開發新實作時準備佇列與背景程序,等新實作整合後,再取得對應版本部署到測試環境,與 Mandy 一起驗證背景寄送流程。在驗證通過前,正式功能仍使用原本的寄信方式。

如下圖所示,建立 Sender 介面後,調整呼叫端與新增寄信實作可以並行,各自驗證並整合到主幹,Brent 也可以同步準備背景寄送環境。切換前,再確認這些程式與環境能一起運作,以及恢復原本綁定時的行為。必要驗證通過、正式環境準備好後,才能部署切換版本。

https://ithelp.ithome.com.tw/upload/images/20261002/20102562rg3HvkkzfQ.png

小結

依賴注入讓通知功能使用外部提供的寄信物件,容器綁定則集中決定提供哪個實作。這樣可以保留原本寄信方式,同時逐步整合新的程式與測試。

目前已經能在同一個版本裡使用兩種實作。接下來除了檢查程式選擇的路徑,也要走完實際背景寄送與失敗處理流程,才能切換正式功能。

參考資料


上一篇
Day 17:抽象分支
下一篇
Day 19:透過抽象分支驗證與切換新實作
系列文
重新認識主幹開發(Trunk-Based Development) 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言