在典型的分層式架構下,遇上複雜的業務邏輯時,服務層的不同服務間仍然很容易產生依賴。這篇文章會循序漸進地說明依賴關係的問題,以及如何使用 Rx.NET(Reactive Extensions for .NET)優雅地解耦這些依賴。
問題:高度耦合
高度依賴版本
在沒有使用任何模式的情況下,訂單服務(OrderService)需要直接依賴 IStockService 和 IAccountingService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class OrderService( ILogger<OrderService> logger, IStockService stockService, IAccountingService accountingService) : IOrderService { public void Confirm(Guid orderId) { logger.LogInformation($"Order confirming: {orderId}");
stockService.Ship(Guid.NewGuid(), 10);
accountingService.Credit(100m, PaymentMethod.CreditCard);
logger.LogInformation($"Order confirming: {orderId}, doing something else.");
logger.LogInformation($"Order confirmed: {orderId}"); } }
|
這種設計方式存在幾個問題:
- 強依賴:
OrderService 直接依賴了 IStockService 和 IAccountingService。在分層架構下同層間水平依賴是一個不好的現象。
- 循環依賴風險:在大型專案中,依賴管理不當容易產生循環依賴。
在 Controller 中組合不同的服務是一種可行方案,但不總是適當。例如,在執行 Confirm 方法的中途需要呼叫其他服務,之後繼續執行剩餘邏輯,此時把 Confirm 方法拆分成多個方法就不適合了。
方案一:使用 Rx.NET 發佈訂閱(含副作用)
有副作用的解耦版本
在這個版本中,我們引入 Rx.NET 的 Subject<T>。由訂閱者(Controller 中的訂閱相關程式碼)來管理訂閱細節,從 OrderService 發佈訂單確認的事件,這樣 OrderService 就可避免直接依賴其他服務。
修改後的 Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| [ApiController] [Route("[controller]/[Action]")] public class OrderController( IOrderService orderService, IStockService stockService, IAccountingService accountingService) : ControllerBase { [HttpGet] public IActionResult Confirm() { var subject = new Subject<OrderConfirming>();
subject.Subscribe(topic => { foreach (var product in topic.Products) { stockService.Ship(product.Id, product.Quantity); } });
subject.Subscribe(topic => { foreach (var product in topic.Products) { var amount = product.UnitPrice * product.Quantity; accountingService.Credit(amount, product.PaymentMethod); } });
orderService.Confirm(Guid.NewGuid(), subject); return Ok(); } }
|
修改後的 OrderService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public class OrderService( ILogger<OrderService> logger) : IOrderService { public void Confirm(Guid orderId, IObserver<OrderConfirming> observer) { logger.LogInformation($"Order confirming: {orderId}");
var message = new OrderConfirming { Products = new List<OrderConfirming.Product> { new OrderConfirming.Product { Id = Guid.NewGuid(), Quantity = 10, PaymentMethod = PaymentMethod.CreditCard, UnitPrice = 9.99m, }, new OrderConfirming.Product { Id = Guid.NewGuid(), Quantity = 5, PaymentMethod = PaymentMethod.CashOnDelivery, UnitPrice = 19.99m, } } };
observer.OnNext(message);
logger.LogInformation($"Order confirming: {orderId}, doing something else.");
logger.LogInformation($"Order confirmed: {orderId}"); } }
|
優點:
OrderService 不再依賴具體的服務,只依賴 IObserver<T>
- 解耦了服務間的直接呼叫關係
缺點:
- 職責分離不清:發佈訂閱邏輯散落在 Controller 中,無法完全職責分離
- 維護性差:相關程式碼分散在多個 Controller 中,修改時難以統一管理
- API 設計不夠簡潔:
OrderService 需要增加一個 observer 參數
方案二:優雅的解耦方案(推薦)
優雅解耦版本
這個版本的核心概念是:將訂閱邏輯獨立封裝成專門的訂閱者類別,透過 DI(依賴注入)將訂閱者注入到服務中。
核心類別:ObserverBase
首先,我們建立一個通用的觀察者基底類別:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class ObserverBase<T> : IObserver<T> { private IObserver<T> _observerSubject;
protected IObservable<T> ObservableSubject { get; }
protected ObserverBase() { var subject = new Subject<T>(); ObservableSubject = subject; _observerSubject = subject; }
public void OnCompleted() => _observerSubject.OnCompleted();
public void OnError(Exception error) => _observerSubject.OnError(error);
public void OnNext(T value) => _observerSubject.OnNext(value); }
|
專門的訂閱者類別 OrderConfirmingSubscriber
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class OrderConfirmingSubscriber : ObserverBase<OrderConfirming>, IObserver<OrderConfirming> { public OrderConfirmingSubscriber( IStockService stockService, IAccountingService accountingService) { base.ObservableSubject.Subscribe(topic => { foreach (var product in topic.Products) { stockService.Ship(product.Id, product.Quantity); } });
base.ObservableSubject.Subscribe(topic => { foreach (var product in topic.Products) { var amount = product.UnitPrice * product.Quantity; accountingService.Credit(amount, product.PaymentMethod); } }); } }
|
簡化的 OrderService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class OrderService( ILogger<OrderService> logger, IObserver<OrderConfirming> orderConfirmingObserver) : IOrderService { public void Confirm(Guid orderId) { logger.LogInformation($"Order confirming: {orderId}");
var message = new OrderConfirming { Products = new List<OrderConfirming.Product> { new OrderConfirming.Product { Id = Guid.NewGuid(), Quantity = 10, PaymentMethod = PaymentMethod.CreditCard, UnitPrice = 9.99m, }, new OrderConfirming.Product { Id = Guid.NewGuid(), Quantity = 5, PaymentMethod = PaymentMethod.CashOnDelivery, UnitPrice = 19.99m, } } };
orderConfirmingObserver.OnNext(message);
logger.LogInformation($"Order confirming: {orderId}, doing something else.");
logger.LogInformation($"Order confirmed: {orderId}"); } }
|
簡化的 Controller
1 2 3 4 5 6 7 8 9 10 11 12
| [ApiController] [Route("[controller]/[Action]")] public class OrderController(IOrderService orderService) : ControllerBase { [HttpGet] public IActionResult Confirm() { orderService.Confirm(Guid.NewGuid()); return Ok(); } }
|
DI 設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(); builder.Services.AddScoped<IOrderService, OrderService>(); builder.Services.AddScoped<IStockService, StockService>(); builder.Services.AddScoped<IAccountingService, AccountingService>();
builder.Services.AddScoped<IObserver<OrderConfirming>, OrderConfirmingSubscriber>();
var app = builder.Build(); app.UseAuthorization(); app.MapControllers(); app.Run(); }
|
優點:
- 職責清晰:Controller、Service、Subscriber 各司其職
- 易於維護:所有訂閱邏輯集中在
OrderConfirmingSubscriber 中,修改時無需觸及 Controller 和 Service
- 簡潔的 API:
OrderService.Confirm() 簡潔,沒有額外的 observer 參數
- 易於擴展:新增其他訂閱者只需維護訂閱者類別,無需修改現有程式碼
- 可測試性:各個組件可以獨立測試
結論
通過對比三個版本,我們可以看到服務解耦的逐步演進:
| 特性 |
V1(高度耦合) |
V2(有副作用解耦) |
V3(優雅解耦) |
| 服務間依賴 |
強耦合 |
低耦合 |
低耦合 |
| 訂閱邏輯位置 |
N/A |
Controller |
Subscriber 類別 |
| 職責分離 |
差 |
一般 |
優秀 |
| API 簡潔度 |
好 |
差 |
優秀 |
| 維護性 |
差 |
一般 |
優秀 |
| 可擴展性 |
差 |
一般 |
優秀 |
在大型專案中,雖然 V3 版本初看似更複雜,但帶來的可維護性、易管理性和可擴展性的提升,會在長期維護中帶來顯著的收益。
參考