In der letzten Folge dieser Architektur-Serie, haben wir die Phasen Design und Development kennengelernt. Bei der Design-Phase wurden die jeweiligen Prozesse zu Komponenten abstrahiert. Aus den Komponenten entstanden die jeweiligen Schichten und Contracts (Interfaces). Auch wenn der Punkt Development nicht komplett abgeschlossen wurde, somit ist zumindest die Hauptlogik durch TDD implementiert worden.
In diesem Teil wird die Development-Phase fortgesetzt, womit das Ziel mit dem Spiel „GoFish“ immer näher rückt.
Erstellen von Workflow Foundation Activities
Erst einmal kommt die Frage auf, wofür die Workflow Foundation verwenden? Die Workflow Foundation ist eine Art Framework für Geschäftslogik. Das Verhalten und die Verwendung der jeweiligen Prozesse werden visuell dargestellt. Gerade bei größeren Änderungen der Geschäftslogik ist eine visuelle Darstellung geradezu ideal. Es ist ersichtlicher ein paar Bausteine auszutauschen als Methoden und deren IF-Statements zu durchforsten. Außerdem kann man seine eigenen Komponenten ohne hohen Aufwand zu Activities erweitern. Sollte irgendwann zur Projektlaufzeit doch wieder auf klassischen Code gewechselt werden wollen, kann man einfach die Activities wieder löschen und die eigenen Komponenten bleiben unberührt. Activities dienen demnach als Adapter- oder Fassade-Pattern (siehe das Entwurfsmuster Adapterund Entwurfsmuster Fassade).
In der Design-Phase wurde das Komponentendiagramm entworfen. Dieses beschreibt bereits die jeweiligen Activities für die Geschäftslogik. Das Komponentendiagramm wurde anhand des Ablaufdiagramms abstrahiert.
Abbildung 1 – Das Komponentendiagramm mit den gewünschten Activities.
Das Komponentendiagramm beschreibt die zu benötigten Activities:
- AddCardsToPlayerActivity.cs
- FindQuartetsActivity.cs
- GetCardFromStackActivity.cs
- GetCardsActivity.cs
- GetSameCardValuesActivity.cs
- GetTheWinnerActivity.cs
- RemoveCardsActivity.cs
- SelectRandomCardActivity.cs
- SharedCardsToPlayerActivity.cs
Die Grundlagen für das Erstellen von Workflow Activities habe ich bereits gebloggt bzw. Microsoft Webcasts veröffentlicht. Es wird von der Basisklasse CodeActivity abgeleitet und das Input / Output via Arguments definiert. Das Input / Output entnehmen wir vom DomainModel.
Als Häuptling entspricht hier der GamblingTable, der als Aggregate (Domain-Driven Design) für die Entities Player und Card dient. Aggregate sind Zusammenfassungen von Entitäten und Wertobjekten und deren Assoziationen untereinander zu einer gemeinsamen transaktionalen Einheit.
Beim Spiel stehen für alle Activities dann alle relevanten Daten zur Verfügung. So entwarf ich je nach Anforderungen den I/O folgendermaßen:
AddCardsToPlayerActivity
- IN PlayerCards
- IN CardsToAdd
- OUT ResultPlayerCards
FindQuartetsActivity
- IN PlayerCards
- OUT ResultQuartets
GetCardFromStackActivity
- IN CardDeck
- IN PlayerCards
- OUT ResultCardDeck
- OUT ResultPlayerCards
GetCardsActivity
GetSameCardValuesActivity
- IN PlayerCards
- IN SelectedCard
- OUT SameValueCards
GetTheWinnerActivity
- IN Players
- OUT Result (string)
RemoveCardsActivity
- IN PlayerCards
- IN CardsForDelete
- OUT ResultPlayerCards
SelectRandomCardActivity
- IN PlayerCards
- OUT Result (Card)
SharedCardsToPlayerActivity
- IN Cards
- IN Players
- OUT CardsResult
- OUT PlayersResult
Und hier sind die Implementierungen der Activities:
AddCardsToPlayerActivity
1: using System.Activities;
2: using System.Collections.Generic;
3: using GoFish.Common.Entities;
4:
5: namespace GoFish.Domain.Activities
6: {
7: public class AddCardsToPlayerActivity : CodeActivity
8: {
9: [RequiredArgument]
10: public InArgument<List<Card>> PlayerCards { get; set; }
11:
12: [RequiredArgument]
13: public InArgument<List<Card>> CardsToAdd { get; set; }
14:
15: [RequiredArgument]
16: public OutArgument<List<Card>> ResultPlayerCards { get; set; }
17:
18: protected override void Execute(CodeActivityContext context)
19: {
20: List<Card> playerCards = PlayerCards.Get(context);
21: List<Card> cardsToAdd = CardsToAdd.Get(context);
22:
23: cardsToAdd.ForEach(playerCards.Add);
24:
25: ResultPlayerCards.Set(context, playerCards);
26: }
27: }
28: }
FindQuartetsActivity
1: using System.Activities;
2: using System.Collections.Generic;
3: using GoFish.Common.Entities;
4: using GoFish.Common.Extensions;
5:
6: namespace GoFish.Domain.Activities
7: {
8: public class FindQuartetsActivity : CodeActivity
9: {
10: [RequiredArgument]
11: public InArgument<List<Card>> PlayerCards { get; set; }
12:
13: [RequiredArgument]
14: public OutArgument<List<Card>> ResultQuartets { get; set; }
15:
16: protected override void Execute(CodeActivityContext context)
17: {
18: List<Card> playerCards = PlayerCards.Get(context);
19: List<Card> results = playerCards.FindQuartets();
20:
21: ResultQuartets.Set(context, results);
22: }
23: }
24: }
GetCardFromStackActivity
1: using System;
2: using System.Activities;
3: using System.Collections.Generic;
4: using GoFish.Common.Entities;
5:
6: namespace GoFish.Domain.Activities
7: {
8: public class GetCardFromStackActivity : CodeActivity
9: {
10: [RequiredArgument]
11: public InArgument<List<Card>> CardDeck { get; set; }
12:
13: [RequiredArgument]
14: public InArgument<List<Card>> PlayerCards { get; set; }
15:
16: [RequiredArgument]
17: public OutArgument<List<Card>> ResultCardDeck { get; set; }
18:
19: [RequiredArgument]
20: public OutArgument<List<Card>> ResultPlayerCards { get; set; }
21:
22: protected override void Execute(CodeActivityContext context)
23: {
24: List<Card> cardDeck = CardDeck.Get(context);
25: List<Card> playerCards = PlayerCards.Get(context);
26:
27: Random random = new Random();
28: int deckIndex = cardDeck.Count - 1;
29: Card pulledCard = cardDeck[random.Next(0, deckIndex)];
30:
31: playerCards.Add(pulledCard);
32: cardDeck.Remove(pulledCard);
33:
34: ResultCardDeck.Set(context, cardDeck);
35: ResultPlayerCards.Set(context, playerCards);
36: }
37: }
38: }
GetCardsActivity
1: using System.Activities;
2: using System.Collections.Generic;
3: using GoFish.Common.Entities;
4:
5: namespace GoFish.Domain.Activities
6: {
7: public class GetCardsActivity : CodeActivity<List<Card>>
8: {
9: protected override List<Card> Execute(CodeActivityContext context)
10: {
11: CardsGenerator cardsGenerator = new CardsGenerator();
12: return cardsGenerator.GetCards();
13: }
14: }
15: }
GetSameCardValuesActivity
1: using System.Activities;
2: using System.Linq;
3: using System.Collections.Generic;
4: using GoFish.Common.Entities;
5: using GoFish.Common.Extensions;
6:
7: namespace GoFish.Domain.Activities
8: {
9: public class GetSameCardValuesActivity : CodeActivity
10: {
11: [RequiredArgument]
12: public InArgument<List<Card>> PlayerCards { get; set; }
13:
14: [RequiredArgument]
15: public InArgument<Card> SelectedCard { get; set; }
16:
17: [RequiredArgument]
18: public OutArgument<List<Card>> SameValueCards { get; set; }
19:
20: protected override void Execute(CodeActivityContext context)
21: {
22: List<Card> playerCards = PlayerCards.Get(context);
23: Card selectedCard = SelectedCard.Get(context);
24:
25: List<Card> sameValueCards = playerCards.GetSameValueCards(selectedCard.Value);
26:
27: SameValueCards.Set(context, sameValueCards);
28: }
29: }
30: }
GetTheWinnerActivity
1: using System.Activities;
2: using System.Collections.Generic;
3: using GoFish.Common.Entities;
4: using GoFish.Common.Extensions;
5:
6: namespace GoFish.Domain.Activities
7: {
8: public class GetTheWinnerActivity : CodeActivity<string>
9: {
10: public InArgument<List<Player>> Players { get; set; }
11:
12: protected override string Execute(CodeActivityContext context)
13: {
14: List<Player> players = Players.Get(context);
15:
16: return players.GetTheWinner();
17: }
18: }
19: }
RemoveCardsActivity
1: using System.Activities;
2: using System.Collections.Generic;
3: using GoFish.Common.Entities;
4:
5: namespace GoFish.Domain.Activities
6: {
7: public class RemoveCardsActivity : CodeActivity
8: {
9: public InArgument<List<Card>> PlayerCards { get; set; }
10: public InArgument<List<Card>> CardsForDelete { get; set; }
11: public OutArgument<List<Card>> ResultPlayerCards { get; set; }
12:
13: protected override void Execute(CodeActivityContext context)
14: {
15: List<Card> playerCards = PlayerCards.Get(context);
16: List<Card> cardsForDelete = CardsForDelete.Get(context);
17:
18: cardsForDelete.ForEach(card => playerCards.Remove(card));
19:
20: ResultPlayerCards.Set(context, playerCards);
21: }
22: }
23: }
SelectRandomCardActivity
1: using System;
2: using System.Activities;
3: using System.Collections.Generic;
4: using GoFish.Common.Entities;
5:
6: namespace GoFish.Domain.Activities
7: {
8: public class SelectRandomCardActivity : CodeActivity<Card>
9: {
10: public InArgument<List<Card>> PlayerCards { get; set; }
11:
12: protected override Card Execute(CodeActivityContext context)
13: {
14: List<Card> playerCards = PlayerCards.Get(context);
15: Random random = new Random();
16:
17: int playerIndex = 0;
18:
19: if(playerCards.Count > 0)
20: playerIndex = playerCards.Count - 1;
21:
22: int randomIndex;
23:
24: if (playerCards.Count != 0)
25: randomIndex = random.Next(0, playerIndex);
26: else
27: return new Card();
28:
29: return playerCards[randomIndex];
30: }
31: }
32: }
SharedCardsToPlayerActivity
1: using System.Activities;
2: using System.Collections.Generic;
3: using GoFish.Common.Extensions;
4: using GoFish.Common.Entities;
5:
6: namespace GoFish.Domain.Activities
7: {
8: public class SharedCardsToPlayerActivity : CodeActivity
9: {
10: [RequiredArgument]
11: public InArgument<List<Card>> Cards { get; set; }
12:
13: [RequiredArgument]
14: public InArgument<List<Player>> Players { get; set; }
15:
16: [RequiredArgument]
17: public OutArgument<List<Card>> CardsResult { get; set; }
18:
19: [RequiredArgument]
20: public OutArgument<List<Player>> PlayersResult { get; set; }
21:
22: protected override void Execute(CodeActivityContext context)
23: {
24: List<Card> cards = Cards.Get(context);
25: List<Player> players = Players.Get(context);
26:
27: players.ForEach(player => player.Cards = cards.GetFiveRandomCards());
28:
29: CardsResult.Set(context, cards);
30: PlayersResult.Set(context, players);
31: }
32: }
33: }
Zusammenfassung und Vorschau auf den nächsten Teil
Jetzt sind die wichtigsten Funktionen/Aktivitäten als eigenständige Workflow Activities gekapselt. Diese könnten nun nachträglich mit Unit-Tests abgesichert werden. Jedoch reicht mir für diese Architektur-Serie der TDD-Part (siehe letzte Folge).
Beim nächsten Teil werden wir die Development-Phase fortsetzen und alle Bausteine (Activities) zu Workflows zusammenführen. Sodass, das Aktivitätsdiagramm aus der Design-Phase implementiert wird.
Viel Spaß und bis zur nächsten Folge, wenn Ihr mögt!
Wenn ihnen der Artikel gefallen hat oder er für sie hilfreich war, bitten "kicken" sie ihn.
