1. 程式人生 > >多場景搶紅包業務引發.NETCore下使用介面卡模式實現業務介面分離

多場景搶紅包業務引發.NETCore下使用介面卡模式實現業務介面分離

事情的起因

我們公司現有一塊業務叫做搶紅包,最初的想法只是實現了一個初代版本,就是給指定的好友單發紅包,隨著業務的發展,發紅包和搶紅包的場景也越來越多,目前主要應用的場景有:單聊發紅包、群聊發紅包、名片發紅包、直播場景中的主播發紅包/觀眾給主播發紅包/定時搶紅包,接下來,如果出現其它產品的業務,也將大概率的出現搶紅包的需求。

大同小異的搶紅包業務

紅包的場景無論怎麼變化,其核心演算法不變,這部分是可以抽象的內容,隨著迭代發展,我們之前通常都是通過增加紅包的型別(業務)來擴充套件,但是隨著肉眼可見的發展,部分業務的改動如果需要對紅包業務進行調整和優化對話,將有可能產生牽一髮而動全身的debuff效果。

新的改變

其實這些業務程式碼早該優化一下,我就是懶+忙(藉口),正好有位新同事入職,這塊的優化任務就交給他來做了,從頭到尾我都沒有參與(不知道有沒有吐槽我的程式碼,捂臉~),我初步看了一下,程式碼的實現質量還是挺高的,正好也是一個比較好的應用場景,我就簡單實現一下他做的介面卡模式,徹底的將各個紅包業務型別分離,很好的實現了設計模式的開閉原則,加入某天某個場景的搶紅包業務下線了,這種做法是非常有利於業務的擴充套件和維護。

定義搶紅包介面

public interface IRedPacket
{
    string Name { get; }
    string Put(int org_id, int money, int count, string reason);
    string Get(int id);
}

以上介面包含一個屬性和2個方法,用於設定業務名稱和收發紅包。初次之外,我們還需要定義一個實現業務的基類,用於處理公共業務。

紅包基類業務實現

public abstract class RedPacket : IRedPacket
{
    public abstract string Name { get; }
    public abstract string Put(int org_id, int money, int count, string reason);
    public abstract string Get(int id);

    protected string Create(string reason, int money, int count)
    {
            Console.WriteLine("建立了紅包:{0},金額:Money:{1},數量:{2}", reason, money, count);
            return "成功";
    }

    protected string Fighting()
    {
            Console.WriteLine("呼叫了搶紅包方法:{0}", nameof(Fighting));
            return "成功";
    }
}

在基類中,我們選擇不實現介面,將介面方法定義為抽象型別。同時,定義並實現兩個受保護的方法 Create(建立紅包)/Fighting(搶紅包),介面方法由子類實現具體的業務細節,當子類針對具體的業務細節實現完成後,他們應該會呼叫Create(建立紅包)/Fighting(搶紅包)的方法,直至最終完成整個紅包的流程。

實現單聊紅包

 public class ChatOneRedPacket : RedPacket
 {
     public override string Name { get; } = "ChatOne";
     public override string Put(int org_id, int money, int count, string reason)
     {
         Console.WriteLine("檢查接收人ID:{0}是否存在", org_id);
         return base.Create(reason, money, count);
     }

     public override string Get(int id)
     {
         Console.WriteLine("檢查紅包ID:{0},是否具有領取資格", id);
         return base.Fighting();
     }
 }

群聊紅包

public class ChatGroupRedPacket : RedPacket
{
    public override string Name { get; } = "ChatGroup";
    public override string Put(int org_id, int money, int count, string reason)
    {
        Console.WriteLine("檢查群ID:{0},是否存在", org_id);
        return base.Create(reason, money, count);
    }

    public override string Get(int id)
    {
        Console.WriteLine("檢查是否群ID:{0},當前使用者是否群成員", id);
        return base.Fighting();
    }
}

直播紅包

public class LiveRedPacket : RedPacket
{
    public override string Name { get; } = "Live";
    public override string Put(int org_id, int money, int count, string reason)
    {
        Console.WriteLine("檢查直播ID:{0}是否存在", org_id);
        return base.Create(reason, money, count);
    }

    public override string Get(int id)
    {
        Console.WriteLine("檢查紅包ID:{0} 是否當前主播紅包", id);
        return base.Fighting();
    }
}

為了方便演示,上面的三種紅包子類僅簡單的實現類屬性 Name="ChatOne",除此之外,還實現類介面的收發紅包介面,子類實現 Name 屬性主要是便於我們在DI中去靈活的區分呼叫的主體,實現業務的分離。除了單聊紅包外,我們還有群聊和直播紅包,都採用上面的處理方式,只是各自實現的 Name 屬性時,指定不同的名字即可。在介面實現的方法中,各自的業務還需要執行不同的業務檢查,比如單聊紅包就需要檢查接收人是否存在,群聊紅包還需要檢查群是否存在,該群是否被凍結等等,直播紅包需要檢查主播是否在直播中,觀眾是否在直播房間內,這些都是不同業務場景產生的特殊的業務處理需求。

建立容器例項

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(typeof(IRedPacket), typeof(ChatOneRedPacket))
            .AddScoped(typeof(IRedPacket), typeof(ChatGroupRedPacket))
            .AddScoped(typeof(IRedPacket), typeof(LiveRedPacket));
    ...
}

容器例項的建立非常簡單,只需要將已實現 IRedPacket 介面的子類註冊到服務管道即可。

依賴注入,以例項集的方式

[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
    private readonly IEnumerable<IRedPacket> redpackets;
    public HomeController(IEnumerable<IRedPacket> redpackets)
    {
        this.redpackets = redpackets;
    }
}

通過建立一個控制檯 HomeController 用於演示,在 HomeController 的構造方法中,使用 IEnumerable 獲得在服務中建立的所有實現介面 IRedPacket 的例項。下面將在 HomeController 中 建立兩個介面進行演示發紅包/搶紅包。

發紅包

[HttpPost]
public ActionResult<string> Post([FromBody] RedPacketViewModel model)
{
    var rp = this.redpackets.Where(f => f.Name == model.Type).FirstOrDefault();
    if (rp == null)
    {
        var msg = $"紅包業務型別:{model.Type}不存在";
        Console.WriteLine(msg);
        return msg;
    }
    var result = rp.Put(model.Org_Id, model.Money, model.Count, model.Reason);
    return result;
}

為了演示方便,我們構造4中不同的業務實體去呼叫發紅包的介面,分別將結果輸出到客戶端

// 單聊紅包
{
  "type":"ChatOne",
  "org_id":1,
  "money":8,
  "count":1,
  "reason":"恭喜發財,大吉大利!"
}

// 群聊紅包
{
  "type":"ChatGroup",
  "org_id":2,
  "money":9,
  "count":3,
  "reason":"恭喜發財,大吉大利!"
}

// 直播紅包
{
  "type":"Live",
  "org_id":3,
  "money":8,
  "count":1,
  "reason":"恭喜發財,大吉大利!"
}

//圈子紅包
{
  "type":"Quanzi",
  "org_id":4,
  "money":8,
  "count":1,
  "reason":"恭喜發財,大吉大利!"
}

輸出結果為:

// 單聊紅包

檢查接收人ID:1是否存在
紅包型別:ChatOne,建立了紅包:恭喜發財,大吉大利!,金額:Money:8,數量:1

// 群聊紅包
檢查群ID:2,是否存在
紅包型別:ChatGroup,建立了紅包:恭喜發財,大吉大利!,金額:Money:9,數量:3

// 直播紅包
檢查直播ID:3是否存在
紅包型別:Live,建立了紅包:恭喜發財,大吉大利!,金額:Money:8,數量:1

//圈子紅包

紅包業務型別:Quanzi不存在

搶紅包

[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
    // 生產環境下,該紅包訊息應該是從資料庫中讀取
    var model = GetRedPacket(id);
    var rp = this.redpackets.Where(f => f.Name == model.Type).FirstOrDefault();
    var result = rp.Get(id);
    return result;
}

private RedPacketViewModel GetRedPacket(int id)
{
    int type = --id;
    string[] redPackets = { "ChatOne", "ChatGroup", "Live" };
    var model = new RedPacketViewModel
    {
        Count = 3,
        Money = 8,
        Org_Id = 115,
        Reason = "恭喜發財,大吉大利!",
        Type = redPackets[type]
    };
    return model;
}

搶紅包的過程,傳入一個紅包ID,然後跟進該ID到資料庫進行查詢,得到紅包後,根據紅包型別找出 IRedPacket 的實現類,並進行呼叫,完成搶紅包的操作。可能有的同學會覺得比較奇怪,為什麼不直接拆紅包呢?這是因為我們要根據紅包設計的初衷,不同的紅包,其所執行的業務規範性檢查是不同的,不能直接進行暴力拆包。

結束語

上面我們建立了3個IRedPacket的實現類,並將他們註冊到服務管道中,然後在HomeController中獲得服務依賴注入的例項物件,通過在不同的引數傳入,實現了不同的紅包業務場景的拆分,很好的實現了設計模式中所說的開閉原則。

演示程式碼下載

https://github.com/lianggx/Examples/tree/master/Ron.RedPacketTest