我們所要面臨的第一道難題,也是多租戶系統架構的最大難題 - 怎樣的系統架構才能符合表面上是一套系統,實際上卻能適應各家養雞場的客製化要求?而且需要易於擴充修改,方便維護,又不會增加開發人員額外的工作呢?
解決方案一
通常對於如何同中求異,最直覺的解決方案便是使用判斷式了,像是...
if (User.IsChickenFarm('Aaa'))
{
balabala...
}
else if (User.IsChickenFarm('Baa'))
{
balabala...
}
有開發經驗的人應該都吃過這種寫法的苦頭(XD),在某幾段程式裡偶爾為之還可以,要是系統裡的所有商業邏輯的地方都有這種判斷式,日後若運氣不好需要再新增其他家養雞場時,就得先找出所有的判斷式,再將判斷式的程式碼全改過,根據莫非定律「凡是可能出錯的事必定會出錯」,再怎麼謹慎仔細,一定會有不小心漏改的地方。另外這種寫法十分容易引發開發人員心中的小惡魔,寫完第一家養場雞後,將所以的程式碼複製貼上到第二家的段落法,然後再修改當中差異的部份。結果便是有一大堆的程式碼,但是當中有八九成的部份都是相同的。
解決方案二
對於這種需要動態判斷並抽換商業邏輯的架構,目前最火紅的技術非DI(Dependency injection, 依赖注入)的架構莫屬了,透過將不同的商業邏輯抽取成相同的抽象介面,並藉由替換容器內的物件實作,來達到動態切換不同程式實作。例如我們可以先將商業邏輯抽取出介面。(這邊使用Autofac Container來做說明)
public interfase IChickenService
{
void DoService();
}
public class FarmAaaChickenService : IChickenService
{
public void DoService() { ... }
}
public class FarmBbbChickenService : IChickenService
{
public void DoService() { ... }
}
接著在使用者登入時先針對不同的養雞場來初始化ContainerBuilder。
var builder = new ContainerBuilder();
if (User.IsChickenFarm('Aaa'))
{
builder.RegisterType<FarmAaaChickenService>().As<IChickenService>();
}
else if (User.IsChickenFarm('Bbb'))
{
builder.RegisterType<FarmBbbChickenService>().As<IChickenService>();
}
之後,只要統一透過Container來取得IChickenService的實作,便無需考慮目前使用者規屬的養雞場,並且能夠在相同的程式碼中自動取得正確的商業邏輯實作。
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var service = scope.Resolve<IChickenService>();
service.DoService();
}
或是透過注入的方式於建構時將物件做為參數傳入,Autofac會自動從Container產生正確的實例並且注入建構式。
public class Demo
{
private IChickenService service;
public Demo(IChickenService service)
{
this.service = service;
}
public void Method1()
{
this.service.DoService();
}
}
未完待續...