關於實作整潔架構-探討階層式架構

軟體架構

筆者在看整潔架構實作篇的時候,階層式架構在開發是滿常用的基本設計,但這一個部分其實有一些弱點或是不足地方,不過還是稍微寫一下筆記專門探討這一部分

在軟體開發領域中階層架構算是滿常用的的模式,筆者舉例兩種常見的例子如下

  1. 應用程式階層架構
  2. 網站MVC架構

應用程式階層式架構:在許多軟體應用程式中,常見的架構是將系統分為多個層次,包括用戶介面層、業務邏輯層和資料存取層。用戶介面層處理與使用者的互動,業務邏輯層處理應用程式的邏輯運算,而資料存取層則負責與資料庫或其他資料源進行交互。

網站開發中的MVC架構:MVC(Model-View-Controller)是一種常見的階層式架構,用於開發網站和Web應用程式。Model負責處理數據邏輯和資料存取,View負責處理使用者介面的呈現,Controller負責處理用戶輸入和控制流程。

應用程式階層架構:三層架構

三層式架構的最底層是資料庫,表示層、依賴業務層,業務層又依賴資料存取層。

優點:

  1. 實現分離關注點
  2. 提高程式的可讀性
  3. 可維護性和可擴展性
  4. 它使不同層次的程式碼可以獨立地進行修改和測試
  5. 同時還能夠支援多種用戶介面和資料庫技術的彈性組合

缺點

  1. 耦合性:三層架構中各層之間通常存在相依性,尤其是業務邏輯層和資料存取層之間的耦合性較高。這使得修改或變更其中一個層次可能需要對其他層次進行調整,增加了系統的脆弱性和維護成本。
  2. 過度設計:三層架構常常在設計初期就將系統劃分為三個固定的層次,這可能導致過度設計。當系統需求變化時,可能需要進行大量的重構和修改,增加了開發和測試的時間和成本。
  3. 學習成本:三層架構通常需要開發人員具備多層次的技術知識和技能。對於新入職的開發人員或團隊成員來說,需要花費一定的時間和努力來學習和理解這種架構。

筆者在看實作整潔架構部分,作者該架構最基礎的是資料庫而不是領域邏輯,深深有感觸是因為過往筆者在傳產或是資訊公司的時候,許多地方在需求釐清梳理部分,除了要理解業務流程以外還要定義出資料表的欄位紀錄與表有哪些?

最後筆者覺得文中有一點還不錯的是建模主要對象是針對行為,而不是狀態,對所有應用程式來說狀態很重要事情,但行為才是導致狀態改變原因,也是業務邏輯驅動力

以下是三層架構的基本例子:

using System;

namespace Model
{
    public class Member
    {
        public string UserId { get; set; }
        public string  Password { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Text;
using Model;
namespace Repository.Interface
{
    public interface IMemberRepository
    {
        Member GetByMember(string userid, string password);
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using Model;
using Repository.Interface;

namespace Repository
{
    public class MemberRepository : IMemberRepository
    {
        public List<Member> members = new List<Member>() {
            new Member{ UserId = "test001",
                        Password = "test002",
                        Name ="Eddie",
                        Email = "aaa@gmail.com"}
        };
        public Member GetByMember(string userid, string password)
               => members.Where(m => m.UserId == userid && m.Password == password).FirstOrDefault();

    }
}

 

using System;
using System.Collections.Generic;
using System.Text;

namespace Service.Interface
{
    public interface IMemberService
    {
         bool Login(string userId, string password);
    }
}
using Repository.Interface;
using Service.Interface;
using System;

namespace Service
{
    public class MemberService : IMemberService
    {
        public readonly IMemberRepository _memberRepository;
        public MemberService(IMemberRepository memberRepository)
        {
            _memberRepository = memberRepository;
        }
        public bool Login(string userid, string password)
        {
            var member = _memberRepository.GetByMember(userid, password);
            if (member == null)
                return false;
        
            return true;
        }
    }
}
using NUnit.Framework;
using NSubstitute;
using Repository.Interface;
using Model;
using Service.Interface;
using Service;

namespace Tests
{
    [TestFixture]
    public class MemberTests
    {
        private IMemberRepository _memberRepository;
        private IMemberService _memberService;
        private Member _setReturnMember;
        [SetUp]
        public void Setup()
        {
            _memberRepository = Substitute.For<IMemberRepository>();
            _memberService = new MemberService(_memberRepository);
            _setReturnMember = new Member()
            {
                UserId = "test001",
                Password = "test001",
                Name = "test",
                Email = "abc@gmail.com"
            };
        }

        [Test]
        public void Login_ReturnSuccess()
        {
            string userid = "test001";
            string password = "test001";
            _memberRepository.GetByMember(userid,password).Returns(_setReturnMember);
            //任一參數並回傳多值做法
            //_memberRepository.GetByMember(Arg.Any<string>(), Arg.Any<string>()).ReturnsForAnyArgs
            //                                (_setReturnMember1,
            //                                 _setReturnMember2);
            bool result = _memberService.Login(userid, password);
            Assert.IsTrue(result);
        }

        [Test]
        public void Login_ReturnFail()
        {
            string userid = "test001";
            string password = "test002";
            _memberRepository.GetByMember(userid, password).Returns(m => null);
            bool result = _memberService.Login(userid, password);
            Assert.IsFalse(result);
        }
    }
}

關於偷吃步

作者在文中當中提到有一個叫做破窗理論。

筆者在看破窗理論覺得很有意思是,在軟體開發領域上只要有人開始偷偷跨層或是求方便的狀態,一旦這種先例打開之後,就會被普及接受,因為後面的人在看這程式或是專案的時候,會一樣畫葫蘆繼續開發下去,就會變得相當嚴重。

詳細參閱:https://zh.wikipedia.org/zh-tw/%E7%A0%B4%E7%AA%97%E6%95%88%E5%BA%94

探討架構層的缺陷

在實作的整潔架構當中有提到幾點我滿認同的以下:

1.沒辦法在套件中凸顯出功能邊界出來,以ASP.net MVC為例,在控制器裡面加入UserController、Domain領域當中加入UserService、UserRepository、這些類別檔案沒有使用其他結構輔助亂起來,應用程式不相關功能產生副作用。

2.看不出使用案例,書裡面有提到說AccountService、AccountController實作哪些使用案例是看不出來的。

六角形架構分別有

  1. 實體
  2. 使用案例
  3. 輸出入轉換Port
  4. 輸出入轉接器

來分別探討這些我們常見問題

待續中

 

參考範例

https://github.com/bda605/hexagonal-architecture-acerola

元哥的筆記