.NET-Architektur mit dem Beispiel „GoFish“ – Development–Erstellen von Workflow Activities

by Gregor Biswanger 17. Mai 2012 12:15

gofish-1_thumb1_thumbIn 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.

image

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

  • OUT Result (List<Card>)

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.
kick it on dotnet-kicks.de

Kommentare

Powered by BlogEngine.NET 1.4.5.0
Theme by Extensive SEO

Über den Autor

Gregor Biswanger

Microsoft MVP für Client App Dev
XING

Gregor Biswanger (Microsoft MVP für Client App Dev) ist freier Consultant, Trainer, Autor und Speaker.


Seine Schwerpunkte liegen im Bereich der .NET-Architektur, agilen Prozessen und XAML. Er veröffentlichte vor kurzem seine DVD´s mit Video-Trainings zum Thema „Meine erste Windows 8 App“, „Windows Store Apps mit XAML und C#“ und „WPF 4.5 und Silverlight 5“ bei Addison-Wesley von video2brain.


Biswanger ist auch im Auftrag von Intel GmbH als Technologieberater für die Intel Developer Zone aktiv und ist Leader bei der Ingolstädter .NET Developers Group (INdotNET). 

 

Video über mich:
http://www.youtube.com/watch?v=mx_6SiiLxjk


Basta! 2011 Speaker

CLIPer

MCTS
Windows SharePoint Services 3.0 – Application Development (MCTS)