```
<?php
namespace customized\TeamMissions\Application; //<--LC5
use customized\TeamMissions\Domain\Repository\UserRepository;
use customized\TeamMissions\Domain\Role\Leader;
use customized\TeamMissions\Exception\UserNotFound;
use customized\TeamMissions\Domain\Repository\GroupRepository;
use customized\TeamMissions\Domain\Repository\EventRepository;
use customized\TeamMissions\Exception\EventNotFound;
class GroupService
{
protected $aUserRepository;
protected $aGroupRepository;
protected $aEventRepository;
public function __construct(
UserRepository $aUserRepository,
GroupRepository $aGroupRepository,
EventRepository $aEventRepository
)
{
$this->aUserRepository = $aUserRepository;
$this->aGroupRepository = $aGroupRepository;
$this->aEventRepository = $aEventRepository;
}
public function createGroup(
string $eventId, //<--LC31
string $LINEUserId
)
{
$aEvent = $this->aEventRepository->eventOfId($eventId); //<--LC35
if (!$aEvent) {
throw new EventNotFound();
}
$aLeader = call_user_func(function (string $LINEUserId) { //<--LC41
$aUser = $this->aUserRepository->userOfLINEUserId($LINEUserId);
if (!$aUser) {
throw new UserNotFound();
}
return $aUser;
}, $LINEUserId);
/* @var $aLeader Leader */
$aGroup = $aLeader->createGroup( //<--LC50
$this->aGroupRepository->nextGroupId(),
$aEvent
);
$this->aGroupRepository->saveGroup($aGroup);
}
}
```
---------------------
Comment from Kim Kao -
1. 關於命名的方式
其實一般就是你的限界上下文 或者業務系統的大分類在收容,比較常看到的是以限界上下文作為邏輯上的目錄區分,所以 namespace 會直接對應到限界上下文去
2. 1)關於參數型別的問題
這裡你的兩個參數 分別是 eventId, 以及 LineUserId , 在領域驅動設計中為了要更完整的表達你的業務概念,並且如果你是走強型別的設計的語言平台 (靠邀, PHP 7 以後應該可以說自己強型別了 XD ),那麼把關鍵的業務知識用比較容易看懂的型別來存放會是比較好的, 像是你這邊的 EventID 很明確的表達了他就是每一次的事件點發生時會產生一個 unique id 去做記錄用的,而另一個 LINEUserId. 如果在你的系統中 就是特別的概念,他就是真的是表示 Line 上面的帳號的話,那直接強化他的概念也很好啊,但這裡有個陷阱 ... 就是 "在你的系統中,你是會關心整個 LineUser, 還是只是需要有個 LINEUserId 來做參考而已? " , 如果是前者那麼你可能也會同時有個 LineUser, 而其中含有 LINEUserId ...
2. 2)createGroup 方法,是否應該收納不變條件在這裡
看在這個物件類別叫做 GroupService的份上,他是個 application service ,理論上負責的邏輯只處理邊界上的資料翻譯工作,可以把關於創建群組的不變條件/商業邏輯收到 Group類別的建構子,或者工廠方法裡頭去...
3. User Entity 的設計
首先第一個建議把 User 這個詞換成更適合的名詞,因為 User 實在太寬泛了 XD
至於是否多切一個 interface 才提供給予隊長的能力,我覺得是可以的, 因為你是直接針對隊長的概念與業務能力建模
在你的設計中, Event, User, Group 是三個獨立的聚合, 我想確定我的理解對不對..
4. 覺得這裡的 Event Repository 怪怪的,真的應該要有 Event Repository 嗎?
LC35~ Lc50 這裡的程式碼主要是要去創建 團隊的代碼,這裡有一個地方要關心的是,你的Event 代表的是你的某一檔的活動中的某一個任務,這裡產生了抽象程度的不一致,對你的業務來說其實關心的是每一次的活動,而你只是剛好需要去針對每一個活動中的每一個事件作紀錄,所以總體來說得先問自己 :
> 每一個活動中的事件的生命週期,對我來說很重要嗎,我需要關心他產生前、中、後的各種對應的業務行為嗎?
如果不在乎的話,那其實你就是直接把 eventId 讀回來,設計成 Value Object 形式再搭配使用就可以了
另外關於這個
>而如果還有從其他地方可以拿資料(假設叫 Team Factory),且刻意把 createTeam 實作成 User Entity 的一個工廠方法,那我們要整個 TeamFactory 都一起注入到 User Entity 嗎?(這樣想想又很奇怪,在 Entity 內部又注入東西... 還是這樣很正常XD)
這裡的視角比較怪異,創建團隊的時候是頂多是以 Team or Group 為主要的進入點,建構子會傳入的是團隊成員清單,所以不太會把 teamFactory 給注入到 User Entity, 而且更重要的事情是,每個聚合根之間,不會直接的去訪問、意味著不會有相依關係...
5. 如果只是要檢查 Event Id 是否存在而用不到 Event Entity 的一堆資料,實務上會在 Event Repository 上面另外開一個獨立的方法嗎(可能叫 hasEventOfId())?還是說應該一律從 Repository 取出看有沒有東西?
這真的蠻考驗你實際最終 unique id 的取得方式,如果你就是只能依靠資料庫的流水號取號,或者去計算當前總筆數數量再+1 的,那你只能去資料庫撈,但是不會針對這樣的參考的 value object 特別去開一個 Repository (Repository pattern in DDD is repsonsible for constructing Aggregate root current status only !!!!)
5. 1)如果是後者,如果想要避免檢查的邏輯重複四散的話,我們應該要把檢查 ID 是否存在的邏輯往哪邊放嗎?(開一個 Domain Service 來放?還是這算是 Application Service 的事情...暈)
如果這取號的概念跟事件的紀錄有關 ,你是可以試著用 domain service 來做, application service 通常跟業務邏輯無關
5. 2)Domain Entity 底下的東西會有 Legacy Loading 這件事嗎?
你是指 Lazy Loading ? 是否要做 lazy loading 一班看你獲取的資料量體訴求才來決定要不要做 lazy initialization (loading),但在DDD 的聚合根的世界,一班不太需要 lazy loading, 因為你每一個聚合根幾乎只代表某一個業務實體,除非你的聚合根設計的太大, 肉粽頭一拉整串超大串,那代表要切割了...
---------------------------
> 在你的設計中, Event, User, Group 是三個獨立的聚合, 我想確定我的理解對不對…
我是想把他放進 Group 類別裡面,會想放進去的原因是因為在 GroupRepository 儲存 Group 時,要檢查現在時間是不是還落在起始時間到截止時間之間。
但是但是…Event 在這個 Domain (前台)底下的確不會有任何機會被修改(只會在後台由系統管理員調整名稱啊活動時間什麼的)。
> 你是指 Lazy Loading ? .... 除非你的聚合根設計的太大, 肉粽頭一拉整串超大串,那代表要切割了...
對 XD 明白