軟體架構設計:有『題』- API 設計原則以『線上售票系統為例』
軟體架構設計:有『題』- API 設計原則以『線上售票系統為例』
前言
先前,我有篇文章是以讀過【Get Your Hands Dirty on Clean Archiecture】後的心得感想,這本書的中文名稱是:【Clean Architecture 實作篇:在整潔架構上弄髒你的手】,讀完這本書大約是去年 2022/10 月左右,也是我當時在開發 API 新版框架大爆發的時候,書中給我不少的啟發,在配合實際的專案開發的實作經驗 + 框架設計所需要的框架設計便引入六角架構的設計方式,藉以改良原有的舊版的框架,而這篇『軟體架構設計:無題 (http://gelis-dotnet.blogspot.com/2022/08/blog-post.html)』中便是以完整的〔六角架構 (Hexagonal Architecture)〕並加以解釋如何在六角架構中落實單元測試 Unit Test,這些都是我讀完書後,實際落實在專案上的實際做法 + 分享。
當時的六角架構 + Unit Test 的範例程式在這:https://github.com/wugelis/AdapterInOutLayerSapmle
上次是無題,今天,我以一個X宏線上售票系統為例子,我們從頭到尾地的,Step By Step 的來介紹,這個 API 售票系統的建置過程,而這裡,我也會搭配 『API 設計原則』一書裡的一些做法,也就是說,這裡開發的 API 會使用 API First 的設計樣式,因為這裡的 API 設計的出發點為該企業、在這個商業模式上所能夠提供之『商業能力』回出發點考量,而不是過往傳統的單一客戶(UI)思維的客製化模式考量,所謂的 API First 是轉向為產品化思惟的導向模式,如此也才能發揮 API 在市場上的價值,這也才是 API First 的思維模式。
需求來源
圖(一)、線上售票系統需求
今天的需求來源,是一個線上售票系統,先前我曾在專案中製作過一個售票系統的雛形,這裡我就拿這個範例來以 DDD 來進行戰略建模,並搭配使用我在新竹顧問時所開發的新版 API Framework 來實作並支撐這個售票系統 API 的 Infrastructure Layer,沒錯,所以言下之意,我們以 DDD 來驅動這個售票系統 API 的開發,所以這裡的 Core Domain 就是這個 API 主要的商業能力。
從事件風暴到領域模型
圖(二)、DDD 的戰略建模 - Bounded Context
從圖(一)需求得知在購票流程裡,除了可以線上購票外,購票成功後會發送簡訊通知,這裡我們找到 3 個 Contexts 並確定其系統邊界,這 3 個 Contexts 分別是:購票、管理票卷、簡訊發送。整個購票作業還需要 Generic Subdomain 來支撐,像是我前面提到的 API 的 Framework 會提供 OAuth2 與 JWT Token 的 Infrastructure 的底層能力。
一次只看一種業務問題即是 Problem Space 的主要目標,透過這個方式來看我們的這個(產品/專案)的商業目標,我可以將這些商業目標拆分如下的 Subdomain:
- 購票:Core Domain
- 登入購票網站:Generic Subdomain
- 簡訊通知:Generic Subdomain
- 管理票卷:Supporting Domain
讓領域模型關連到程式碼
圖(三)、透過事件風暴(Event Storming) - 進行建模
這邊管理票卷劉在下次,我先針對 Core Domain 來進行 API First 的建模,這也是售票系統主要提供的商業能力。
圖(四)、DDD 戰術建模 - Domain Modeling
到這裡,我們已經可以進行戰術建模 + 繪製領域模型 Domain Modeling 了,因為經由事件風暴可以得知 UI 應該提供『選擇座位』、『選擇演唱會場次』、『選擇票種』的 Command,這些即可以直接繪製 Domain Modeling 並清楚的表達出劃位 (Seat Reservation) 動作、產生 Ticket 的購票作業,以及相關的 Value Object
程式碼實作
這裡以整潔架構為基底,先刻劃出 Domain Layer:
public interface IAggregateRoot
{
}
public class Entity
{
}
public abstract class ValueObject
{
}
public class Ticket: Entity, IAggregateRoot
{
public Guid Id { get; set; }
public string ReservatName { get; protected set;}
// 確認購票
public int SaveTicket()
{
return 0;
}
// 建立票卷
public static Ticket Create(SeatReservation reserve)
{
return new Ticket() { Id = Guid.NewGuid() };
}
}
// 預定位 Entity
public class SeatReservation: Entity
{
public Guid Id { get; protected set; }
public string ReserveName { get; protected set; }
public ConcertVenue ReserveConcertVenue { get; protected set; }
public DateTime? ShowTime { get; set; }
// 確認與預訂票卷
public Ticket SetReservat(string name)
{
return new Ticket();
}
public string[] GetReserveByDate(TimeSpan reserveRange)
{
return new string[] {};
}
public static SeatReservation Create(string ReserveName)
{
return new SeatReservation() { Id = Guid.NewGuid() };
}
}
public class Membership: Entity
{
}
public class Account: Entity, IAggregateRoot
{
}
public class ConcertVenue: ValueObject
{
}
接著是 Application Services 的實作,這對應到 UX 也可以是一個 Use Case ,我們先以購票這個 Domain Event 來進入,這裡的 Command 有些是 Query,不過因為有購票人就要有 Account 會員身分,最後要建立 Ticket 演唱會票卷,這些就有很多事情要做了。在上方 Domain Layer 我們先建好 Account, Ticket, SeatReservation, ConcertVenue 等 Entity 與 ValueObject 物件,接著,下面的 Application Services 便會參照到這些物件。
Application Services 設計的程式碼如下:
namespace Application.ConcertTickets
{
public interface IReserveRepository
{
int SaveConcertReservation(Ticket ticket);
}
public class ConcertTicketAppService
{
private IReserveRepository _reserveRepository;
public ConcertTicketAppService(IReserveRepository ticketRepository)
{
_reserveRepository = ticketRepository;
}
// 購票作業
public int Reservation(ReserveDTO ticketDto)
{
// 檢核購票時間
// 檢核選擇票種是否還有位子?
// 若選擇時間有票種,進行購票作業(此預定保留 10 分鐘、若未付款,10分鐘取消資格,將釋放此預訂票種給其他訂票作業的 Transaction)
SeatReservation seat = SeatReservation.Create(ticketDto.ReserveID);
// 預訂票卷(此預定預設保留 10 分鐘)
Ticket ticket = seat.SetReservat(ticketDto.ReserveID);
return 0;
}
// 確認定位
public int SaveReservation(Ticket ticket)
{
return _reserveRepository.SaveConcertReservation(ticket);
}
}
}
// DTO 組件
namespace Application.DTO
{
public class ReserveDTO
{
public string ReserveID { get; set; }
public DateTime? ReserveTime { get; set; }
public int? ShowTime {get;set;}
}
}
這裡即為一個透過事件風暴 (Event Stroming) 進行建模、到如何對應到實際程式碼設計的一個過程,這之中包括了整潔架構的軟體架構設計。
因為內容太多了,待續...
留言
張貼留言