Category Archives: Patterns & Practices

Wydajność: HashSet + struktury

Struktury mogą być doskonałym rozwiązaniem w połączeniu ze słownikiem czy HashSet. Zwykle projektuje je się w sposób immutable, więc stanowią naturalny wybór na klucze w HashSet.

Niestety bardzo łatwo popełnić błąd, który poskutkuje gwałtownym spadkiem wydajności.  Załóżmy, że mamy dwie struktury, jedna zawierająca wyłącznie typy proste (value type), a druga również typ referencyjny czyli string:

    public struct WithValueType
    {
        public int ValueType1;
        public int ValueType2;
    }

    public struct WithReferenceType
    {
        public int ValueType;
        public string RefType;
   
    }

Napiszmy teraz prosty benchmark, sprawdzający ile trwa dodanie obiektów do HashSet:

        int times = 1000000;
        
        var withValueType = new HashSet<WithValueType>();
        var withReferenceType = new HashSet<WithReferenceType>();

        // withValueType
        Stopwatch timer = Stopwatch.StartNew();

        for (int i = 0; i < times; i++)
            withValueType.Add(new WithValueType {ValueType1 = i, ValueType2 = i});

        timer.Stop();
        Console.WriteLine($"WithValueType: {timer.ElapsedMilliseconds}");

        // withReferenceType
        timer = Stopwatch.StartNew();

        for (int i = 0; i < times; i++)
            withReferenceType.Add(new WithReferenceType { ValueType = i, RefType = i.ToString() });

        timer.Stop();
        Console.WriteLine($"withReferenceType: {timer.ElapsedMilliseconds}");
      

Na ekranie zobaczymy następujące wyniki:

1

Skąd ta różnica? To już nie jest mikro-optymalizacja, ale bardzo poważny problem wydajnościowy, który ostatnio zmarnował mi sporo czasu na profilowaniu aplikacji. Wiemy, że struktury możemy porównywać poprzez wartość. Jeśli w kodzie mamy warunek np. if(a != b), wtedy konkretne wartości zostaną porównane, a nie jak w przypadku klas, adres instancji. Problem w tym, że wykorzystywany jest mechanizm reflekcji, który zawsze jest wolniejszy. Z dokumentacji MSDN wiemy, że Equals dla struktur działa następująco:

1. Jeśli struktura zawiera przynajmniej jeden typ referencyjny, wtedy jest używany mechanizm reflekcji.
2. Jeśli struktura zawiera wyłącznie typy proste (value types), wtedy dane porównywane są bajt po bajcie.

Zobaczmy implementację ValueType.Equals:

public override bool Equals (Object obj) { 
            BCLDebug.Perf(false, "ValueType::Equals is not fast.  "+this.GetType().FullName+" should override Equals(Object)"); 
            if (null==obj) {
                return false; 
            }
            RuntimeType thisType = (RuntimeType)this.GetType();
            RuntimeType thatType = (RuntimeType)obj.GetType();
 
            if (thatType!=thisType) {
                return false; 
            } 

            Object thisObj = (Object)this; 
            Object thisResult, thatResult;

            // if there are no GC references in this object we can avoid reflection
            // and do a fast memcmp 
            if (CanCompareBits(this))
                return FastEqualsCheck(thisObj, obj); 
 
            FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 
            for (int i=0; i<thisFields.Length; i++) {
                thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);
                thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);
 
                if (thisResult == null) {
                    if (thatResult != null) 
                        return false; 
                }
                else 
                if (!thisResult.Equals(thatResult)) {
                    return false;
                }
            } 

            return true; 
        }

Po metodzie CanCompareBits widzimy, że szybkie sprawdzanie jest wyłącznie możliwe, gdy nie ma typów referencyjnych. Wtedy po prostu są sprawdzane wartości bajtów (szybka operacja). Problemy zaczynają się, gdy dane są przechowywane na stercie, szczególnie w różnych miejscach.

W praktyce, gdy mamy typy referencyjne, wtedy obowiązkowo implementujemy Equals. Powyższe rozważania jednak nie usprawiedliwiają faktu, że to struktura z typami prostymi, a nie referencyjnymi, stanowi największy problem. HashSet zawsze wywołuje GetHashCode. W dokumentacji przeczytamy, że:

        /*=================================GetHashCode==================================
        **Action: Our algorithm for returning the hashcode is a little bit complex.  We look
        **        for the first non-static field and get it's hashcode.  If the type has no
        **        non-static fields, we return the hashcode of the type.  We can't take the 
        **        hashcode of a static member because if that member is of the same type as
        **        the original type, we'll end up in an infinite loop. 
        **Returns: The hashcode for the type. 
        **Arguments: None.
        **Exceptions: None. 
        ==============================================================================*/
        [System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)] 
        public extern override int GetHashCode();

Rozwiązaniem jest własna implementacja GetHashCode:

    public struct WithValueType
    {
        public int ValueType1;
        public int ValueType2;

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public bool Equals(WithValueType other)
        {
            return ValueType1 == other.ValueType1 && ValueType2 == other.ValueType2;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (ValueType1*397) ^ ValueType2;
            }
        }
    }

    public struct WithReferenceType
    {
        public int ValueType;
        public string RefType;

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public bool Equals(WithReferenceType other)
        {
            return ValueType == other.ValueType && string.Equals(RefType, other.RefType);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (ValueType*397) ^ (RefType != null ? RefType.GetHashCode() : 0);
            }
        }
    }

Wyniki prezentują się teraz dużo lepiej:
2

Warto również zaimplementować interfejs IEquatable, jeśli chcemy uniknąć boxingu. Więcej informacji w moim poprzednim wpisie.

CONSUMER DRIVEN CONTRACTS w PACT-NET – implementacja testów dostawcy

W poprzednim wpisie przedstawiłem framework PACT.NET. Zdołaliśmy zaimplementować testy po stronie konsumentów. Wynikiem tego, był wygenerowany plik JSON zawierający nagrane testy. Dla przypomnienia, test po stronie konsumenta wyglądał następująco:

[TestFixture]
public class ConsumerTests
{
    private IMockProviderService _mockProviderService;
    private IPactBuilder _pactProvider;
 
    [OneTimeSetUp]
    public void OneTimeSetUp()
    {
        _pactProvider = new PactBuilder()
            .ServiceConsumer("ConsumerName")
            .HasPactWith("ProviderName");
 
        _mockProviderService = _pactProvider.MockService(3423,false);
    }
 
    [OneTimeTearDown]
    public void OneTimeTearDown()
    {
        _pactProvider.Build();
    }
 
    [Test]
    public void Should_Return_FirstName()
    {
        //Arrange
        _mockProviderService.Given("there are some persons data")
            .UponReceiving("a request to retrieve all persons")
            .With(new ProviderServiceRequest
            {
                Method = HttpVerb.Get,
                Path = "/api/persons"     
            })
            .WillRespondWith(new ProviderServiceResponse
            {
                Status = 200,
                Headers = new Dictionary<string, string>
                {
                    { "Content-Type", "application/json; charset=utf-8" }
                },
                Body = new[]
                {
                    new
                    {
                        FirstName = "Piotr",
                    }
                }
            });
 
        var consumer = new PersonsApiClient("http://localhost:3423");
 
        //Act
        var persons = consumer.GetAllPersons();
 
        //Assert
        CollectionAssert.IsNotEmpty(persons);
        CollectionAssert.AllItemsAreNotNull(persons.Select(x=>x.FirstName));
 
        _mockProviderService.VerifyInteractions();
    }
}

Z kolei wygenerowany JSON:

{
  "provider": {
    "name": "ProviderName"
  },
  "consumer": {
    "name": "ConsumerName"
  },
  "interactions": [
    {
      "description": "a request to retrieve all persons",
      "provider_state": "there are some persons data",
      "request": {
        "method": "get",
        "path": "/api/persons"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": [
          {
            "FirstName": "Piotr"
          }
        ]
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.1.0"
  }
}

Przejdźmy zatem do testu po stronie dostawcy:

    [TestFixture]
    public class ProducerTests
    {
        [Test]
        public void Ensure_That_Consumer_Requirements_Are_Met()
        {
            var config = new PactVerifierConfig();
            IPactVerifier pactVerifier = new PactVerifier(() => { }, () => { }, config);

            pactVerifier
                .ProviderState(
                    "there are some persons data",
                    setUp: DataSetup,tearDown:DataTearDown);
       
            using (var testServer = TestServer.Create<Startup>())
            {
                pactVerifier
                   .ServiceProvider("Persons API", testServer.HttpClient)
                   .HonoursPactWith("Consumer")
                   .PactUri(@"consumername-providername.json")
                   .Verify();
            }
        }

        private void DataTearDown()
        {
        }


        private void DataSetup()
        {
        }

    }

W celu uruchomienia serwera, używamy Microsoft.Owin.Testing czyli klasy TestServer. W momencie odpalenia testu, zostanie zatem stworzony serwer z naszą aplikacją. Następnie “odgrywany” będzie test wygenerowany przez konsumenta. Operujemy na prawdziwych danych, a nie na mock’ach, stąd metody DataSetup oraz DataTearDown w praktyce zawierają odpowiedni kod, dodający oraz usuwający dane testowe.

Po wykonaniu testu, zostanie wygenerowany plik persons_api_verifier.log:

2016-06-01 19:54:01.894 +01:00 [Debug] Verifying a Pact between ConsumerName and ProviderName
  Given there are some persons data
    a request to retrieve all persons
      with GET /api/persons
        returns a response which
          has status code 200
          includes headers
            'Content-Type' with value application/json; charset=utf-8
          has a matching body (FAILED - 1)

Failures:

1) Expected: [
  {
    "FirstName": "Piotr"
  }
], Actual: [
  {
    "FirstName": "Piotr",
    "LastName": "Zielinski"
  },
  {
    "FirstName": "first name",
    "LastName": "last name"
  }
]

Widzimy, że test zakończył się niepowodzeniem, ponieważ oczekiwana zawartość Content jest inna niż ta zwrócona przez prawdziwy serwis.

Za pomocą PathVerifier definiujemy, które testy chcemy wykonać. W naszym przypadku jest tylko jeden. W praktyce definiuje się wiele stanów i kilka plików JSON.

Consumer driven contracts – pact-net

O Consumer-Driven-Contracts pisałem już tutaj. Sam koncept jest dosyć prosty, ale bez odpowiednich narzędzi może być mozolny w wdrożeniu.
Pack-net, jak nie trudno się domyślić jest implementacją biblioteki pact w C#. Kod źródłowy możną znaleźć na GitHub.

Wiemy, aby zaimplementować Consumer-Driven-Contracts (CDC) musimy napisać testy zarówno po stronie konsumenta jak i dostawcy. Zwykle, konsumenci nie testują prawdziwej usługi, a jedynie operują na mock’ach. Następnie dostawca, wykona wszystkie testy konsumenckie na prawdziwej usłudzę. Sztuczka polega na tym, aby w jakiś sposób przekazać do dostawcy testy zdefiniowane przez konsumenta. Nie chcemy generować biblioteki DLL i jej przekazywać. Pact wygeneruje za nas plik JSON, ze wszystkimi testami. Podsumowując za pomocą Pact:

1. Konsumenci definiują klasyczne testy jednostkowe. Wykonywane są one w izolacji na mock’u usługi.
2. Testy jednostkowe są “nagrywane” i  zapisywane w pliku JSON.
3. Plik (lub pliki w przypadku wielu konsumentów) JSON  są odczytywane przez producenta. Nagrane testy są odtwarzane, ale będą już wykonywane na prawdziwej usłudze.

Jeśli tworzymy usługę, wtedy krok trzeci gwarantuje nam, że spełniamy wszystkie wymogi konsumentów. Jeśli konsument A potrzebuje np. pole FirstName w odpowiedzi, wtedy zdefiniuje to za pomocą testu jednostkowego, a dostawca następnie zweryfikuje to.
Pact dostarcza łatwy interfejs i nie musimy się martwić np. odpaleniem serwer’a web w testach dostawcy, które zawsze wykonywane są na prawdziwej usłudze. Podobnie, dzięki Pact łatwo tworzyć mock’a usługi. Po prostu wystarczy zdefiniować oczekiwane nagłówki czy zawartość ciała.

Myślę, że najłatwiej będzie to zrozumieć na przykładzie. Załóżmy, że tworzymy usługę zwracającą listę osób. Nasza solucja zatem będzie składać się z:
1. Provider.Web – usługa
2. Provider.Web.Tests – testy dostawcy (wykonywane na prawdziwej usłudze)
3. Consumer – prosty klient łączący się z usługą
4. Consumer.Tests – testy konsumenckie (wykonywane na mock’u)

W dzisiejszym wpisie zajmiemy się punktami 1,3 oraz 4, a następnym razem zajmiemy się testami dostawcy.

Zacznijmy zatem od dostawcy (punkt 1):

    public class PersonsController : ApiController
    {
        public PersonInfo[] GetAll()
        {
            var person1 = new PersonInfo {FirstName = "Piotr", LastName = "Zielinski"};
            var person2 = new PersonInfo { FirstName = "first name", LastName = "last name" };

            return new PersonInfo[] {person1, person2};
        }
    }

    public class PersonInfo
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

Zwykłe WebApi, którego odpowiedź wygląda następująco:

<ArrayOfPersonInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Provider.Web.Controllers">
<PersonInfo>
<FirstName>Piotr</FirstName>
<LastName>Zielinski</LastName>
</PersonInfo>
<PersonInfo>
<FirstName>first name</FirstName>
<LastName>last name</LastName>
</PersonInfo>
</ArrayOfPersonInfo>

Następnie zdefiniujemy klienta (konsumenta – punkt 3):

   public class PersonsApiClient
    {
        public string BaseUri { get; set; }

        public PersonsApiClient(string baseUri)
        {
            BaseUri = baseUri;
        }

        public PersonInfo[] GetAllPersons()
        {
            var client = new RestClient(BaseUri);
            var persons = client.Get<List<PersonInfo>>(new RestRequest("api/persons")).Data;

            return persons.ToArray();
        }
    }

Ostatni etap w dzisiejszym wpisie to zdefiniowanie testu konsumenta (punkt 4):

    [TestFixture]
    public class ConsumerTests
    {
        private IMockProviderService _mockProviderService;
        private IPactBuilder _pactProvider;

        [OneTimeSetUp]
        public void OneTimeSetUp()
        {
            _pactProvider = new PactBuilder()
                .ServiceConsumer("ConsumerName")
                .HasPactWith("ProviderName");

            _mockProviderService = _pactProvider.MockService(3423,false);
        }

        [OneTimeTearDown]
        public void OneTimeTearDown()
        {
            _pactProvider.Build();
        }

        [Test]
        public void Should_Return_FirstName()
        {
            //Arrange
            _mockProviderService.Given("there are some persons data")
                .UponReceiving("a request to retrieve all persons")
                .With(new ProviderServiceRequest
                {
                    Method = HttpVerb.Get,
                    Path = "/api/persons"      
                })
                .WillRespondWith(new ProviderServiceResponse
                {
                    Status = 200,
                    Headers = new Dictionary<string, string>
                    {
                        { "Content-Type", "application/json; charset=utf-8" }
                    },
                    Body = new[]
                    {
                        new
                        {
                            FirstName = "Piotr",
                        }
                    }
                });

            var consumer = new PersonsApiClient("http://localhost:3423");

            //Act
            var persons = consumer.GetAllPersons();

            //Assert
            CollectionAssert.IsNotEmpty(persons);
            CollectionAssert.AllItemsAreNotNull(persons.Select(x=>x.FirstName));

            _mockProviderService.VerifyInteractions();
        }
    }

Za pomocą klasy PactBuilder budujemy mock usługi czyli IMockProviderService. Następnie definiujemy wejścia i wyjścia mocka. Widzimy, że jeśli zapytanie GET przyjdzie na /api/persons wtedy zwracamy jeden obiekt, zawierający imię. Za pomocą MockProvider możemy zasymulować dowolne zachowanie usługi. W naszym przypadku jest to po prostu GET i odpowiedź. W bardziej zaawansowanych przypadkach dochodzą do tego parametry query, nagłówki, metody POST, DELETE itp.

Mock zwróci zatem tylko jeden obiekt, zawierający imię Za pomocą następnych asercji, zweryfikujemy, że faktycznie imię musi zawierać jakąś treść. Jeśli pole FirstName na tym etapie jest puste, oznacza to, że implementacja klienta jest błędną – np. deserialziacja nie działa tak jak należy. Widzimy, że port 3423 został dobrany losowo, ponieważ nie ma to znaczenia dla mock’a.
VerifyInteractions sprawdzi również tzw. interakcje. W powyższym przypadku oznacza to, że dokładnie jedno zapytanie GET “api/persons” zostało wysłane do usługi. Jeśli nasz klient zawierałby błąd, np. dwukrotne wysłanie zapytania, wtedy test również zakończyłby się błędem.

Po wykonaniu testu, zostanie on nagrany i zapisany w pliku “consumername-providername.json”:

{
  "provider": {
    "name": "ProviderName"
  },
  "consumer": {
    "name": "ConsumerName"
  },
  "interactions": [
    {
      "description": "a request to retrieve all persons",
      "provider_state": "there are some persons data",
      "request": {
        "method": "get",
        "path": "/api/persons"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": [
          {
            "FirstName": "Piotr"
          }
        ]
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.1.0"
  }
}

W kolejnym wpisie zajmiemy się implementacją testów dostawcy, które będą wykorzystywać właśnie wygenerowany plik JSON, w celu wykonania takiego samego testu na prawdziwej usłudze.

DbUp – generowanie silnie typowanych nazw procedur za pomocą T4

O bibliotece DbUp pisałem tutaj:
DBUP – AKTUALIZACJA BAZ DANYCH

Z kolei o szablonach T4  stworzyłem cały mini-cykl, który można znaleźć tutaj.

Problem z DbUp jest taki, że wywołuje on po prostu kolejne skrypty. Następnie sami za pomocą np. Dapper, musimy podać nazwę procedury czy tabeli na której chcemy operować. DbUp + Dapper to częsta kombinacja narzędzi. Jeśli korzystamy z rozbudowanego ORM, takiego jak EntitityFramework, wtedy wszystkie tabele czy nazwy procedur i tak są silnie typowane ponieważ framework je wygeneruje. Za pomocą  EF Migrations, można uzyskać to samo co robi DbUp.

Jeśli jednak korzystamy tylko z Dapper+DbUp, wciąż możemy mieć silnie typowane nazw. Załóżmy że w projekcie DbUp mamy następującą strukturę skryptów:

-> StoredProcs
—>1. GetFirstNameById
—>2. GetData
—>3. UpdateData

->Functions:
—>1. Split
—>2. MaxData

->Types
—>1.  CustomType1
—>2.  CustomType2

Wtedy można pokusić się o następujący szablon T4:

<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #>
namespace Data.Sql
{
<#
	 string[] foldersToGenerate = new [] { "StoreProcs", "Functions","Types" };
	 string scriptsPath = Host.ResolvePath(@"..\Database\Scripts");

	 foreach(var folderName in foldersToGenerate)
	 {
	     WriteLine(string.Format("\tinternal static class {0}\n\t{{",folderName));

		 foreach(var directory in System.IO.Directory.GetDirectories(scriptsPath, "*" + folderName, SearchOption.AllDirectories))
		 {
			foreach(var fileName in Directory.GetFiles(directory))
			{
				string name = Path.GetFileNameWithoutExtension(fileName).Split(' ').Last();
				string declaration = string.Format("\t\tinternal const string {0} = \"{0}\";", name);

				WriteLine(declaration);
			}
		 }

		 WriteLine("\t}");
	 }
#>
}

Szablon przejdzie automatycznie po wszystkich plikach i wygeneruje statyczną klasę zawierającą stałe, np.:

internal static class StoreProcs
{
    internal const string GetFirstNameById="GetFirstNameById";
    internal const string GetData="GetData";
    internal const string UpdateData="UpdateData";
}

Rozwiązanie zakłada, że nazwa pliku pokrywą się z nazwą zasobu (czyli procedury, funkcji czy typu). Analogicznie sprawa wygląda z tabelami. Myślę, że jest to lepsze rozwiązanie niż hardcodowanie za każdym razem procedury.

Wirtualne maszyny a kontenery

Za sprawą Docker, coraz bardziej popularnym terminem staje się “software container”. Jak to bywa z nowikami ostatnio, czasami są one wdrażane na siłę, w środowiskach gdzie nie ma takiej potrzeby.

Na blogu skupiam się na środowisku Windows, dlatego również o kontenerach będę pisał od strony programistów Windows. Najpierw jednak wypada wyjaśnić co to jest kontener?

Wiele lat temu, standardem były fizyczne maszyny. Gwarantowało to oczywiście 100% izolacji między aplikacjami. Aplikacja na komputerze A, nie mogła bezpośrednio adresować pamięci na komputerze B. Z punktu widzenia bezpieczeństwa osiągano w ten sposób wysoką niezależność. Niestety, w momencie gdy chcieliśmy wdrążyć drugi serwis\aplikację, wtedy stawało się to bardzo kosztowne ponieważ musieliśmy zakupić dodatkowy serwer. Można to pokazać następująco:

1

Ogromnym przełomem były wirtualne maszyny. Dzięki nim, na tym samym komputerze, mogliśmy symulować kilka innych systemów operacyjnych. Co ważne, każdy z nich miał swoje zasoby takie jak CPU czy pamieć podręczna. Z punktu widzenia izolacji i bezpieczeństwa, rozwiązanie było znakomite.

2

Nastały jednak czasy, gdzie taka skalowalność nie wystarczała. Wyobraźmy sobie architekturę opartą o mikro-serwisy. Przy dużym ruchu chcemy mieć możliwość aktywowania pewnych węzłów jak szybko tylko to możliwe. Wirtualne maszyny to dosyć ciężkie rozwiązanie. Każda maszyna to osobny system operacyjny. Każdy system z kolei pożera na starcie masę zasobów (CPU, pamieć). Instalacja maszyny również jest dość powolna.

Z pomocą przychodzą kontenery. Można je określić mianem wewnętrznej wirtualizacji. Pojedynczy system zawiera kilka kontenerów, które poziomem izolacji przypominają wirtualne maszyny:

3

Dzięki kontenerom nie mamy overhead, który wiązał się z wirtualną maszyną. Uruchomienie nowego kontenera jest również dużo szybsze niż VM. W wielu wypadkach takie rozwiązanie jest optymalne ponieważ zwykle nie potrzebujemy osobnego systemu, a chodzi nam wyłącznie o izolację. W przypadku mikro-serwisów, kontenery są dobrym rozwiązaniem ponieważ nie potrzebujemy osobnych systemów operacyjnych – wystarczy jeden, wspierający kontenery.

Definiowanie własnych typów danych w C# (statyczne typowanie)

Zwykle programiści korzystają z podstawowych typów dostarczonych przez C#, takich jak String, Int32 czy Double. W świecie programowania obiektowego można jednak pójść o krok dalej i budować własne typy danych. Przeważnie programiści korzystają z nich wyłącznie, gdy do zaimplementowania jest jakaś logika. Dlaczego nie tworzyć ich nawet w sytuacjach, gdy mają one przechowywać wyłącznie dane?

Problem z podstawowymi typami takimi jak String Czy Int32 to fakt, że stanowią one wyłącznie fizyczną, a nie logiczną reprezentację danych. Int32 opisuje każdą liczbę całkowitą, co jest dość szerokim określeniem. Załóżmy, że w kodzie mamy dwie jednostki do opisania:
– prędkość (km/h)
– temperatura (C)

Standardowe rozwiązanie to użycie typów podstawowych, np. Int32:

int velocity = 85;
int temperature = 20;

Niestety powyższy kod nie będzie mógł korzystać z zalet statycznego typowania. Poniższy kod skompiluje się i o błędzie dowiemy się dopiero przy wykonywaniu jakiś operacji:

int velocity = 85;
int temperature = 20;

int result = velocity * temperature;

Prawdopodobnie nie ma sensu mnożyć prędkości przez temperaturę. Analogicznie typy podstawowe nie dostarczają żadnej walidacji logicznej reprezentacji. Co prawda w przypadku liczb, możemy zadeklarować velocity jako uint co ma większy sens ponieważ unikniemy wtedy błędów z związanych z ujemną prędkością. Problem jednak pojawi się w przypadku string:

string email="test@test.com";

W tym przypadku zdecydowanie potrzebna jest walidacja.

Zdefiniujmy zatem typy opisujące logiczną reprezentację danych:

    struct Velocity
    {
        private readonly uint _value;

        public Velocity(uint value)
        {
            _value = value;
        }
    }

    struct Temperature
    {
        private readonly int _value;

        public Temperature(int value)
        {
            _value = value;
        }
    }

Od tej pory, niemożliwe jest popełnienie następującego błędu:

var velocity = new Velocity(43);
var temperature = new Temperature(5);

var result = velocity + temperature;

Kod zakończy się błędem kompilacji: “Operator ‘+’ cannot be applied to operands of type ‘Velocity’ and ‘Temperature'”. Błąd wykryty na etapie kompilacji jest oczywiście dużo bardziej pożądany niż te spotykane już w trakcie działania aplikacji.

Oprócz korzyści z związanych z bezpiecznym typowaniem, kod jest łatwiejszy w zrozumieniu. Typ nadaje kontekst danej wartości.
Dodanie walidacji sprowadza się teraz wyłącznie do:

    struct Temperature
    {
        private readonly int _value;

        public Temperature(int value)
        {        
            _value = value;

            if (!IsValid(value))
                throw new ArgumentException("Temperature must be between -2000 and 2000.");
        }

        private bool IsValid(int value)
        {
            return value < 2000 && value > -2000;
        }
    }

Oczywiście to dopiero początek. W praktyce chcemy mieć do dyspozycji pewne operatory, np.:

            var temperature1 = new Temperature(42);
            var temperature2 = new Temperature(42);
            var temperature3 = new Temperature(41);

            Console.WriteLine(temperature1 == temperature2); // true
            Console.WriteLine(temperature1 == temperature3); // false

Jeśli chcemy wspierać ==, wtedy wystarczy napisać:

    struct Temperature
    {
        private readonly int _value;

        public Temperature(int value)
        {        
            _value = value;

            if (!IsValid(value))
                throw new ArgumentException("Temperature must be between -2000 and 2000.");
        }

        private bool IsValid(int value)
        {
            return value < 2000 && value > -2000;
        }

        public static bool operator ==(Temperature t1, Temperature t2)
        {
            return t1.Equals(t2);
        }

        public static bool operator !=(Temperature t1, Temperature t2)
        {
            return !t1.Equals(t2);
        }
    }

W praktyce warto zadeklarować bazowy typ, który implementuje najczęściej używane operacje (==,!=,>,<,+,-). Nie można również przesadać w drugą stronę i każdą zmienną owijać w typ. Moim zdaniem bardzo szybko taki kod stanie się przykładem overengineering. Jeśli jakiś typ danych występuje bardzo często w kodzie (np. money), wtedy warto o tym pomyśleć.

AKKA.NET – obsługa błędów

W poprzednich postach o AKKA.NET pisałem o hierarchii aktorów. Stanowi ona bardzo ważny element systemów opartych na aktorach. Zasada jest taka, że uproszczamy problem na podproblmy, aż do momentu, gdy każdy podproblem jest łatwy w implementacji za pomocą pojedynczego aktora.  Dla przypomnienia, stworzyliśmy następującą hierarchię:

controller

Co się stanie, gdy jeden z aktorów wyrzuci wyjątek? AKKA.NET posiada kilka mechanizmów. Przede wszystkim jednak, taki aktor zostanie wstrzymany, możliwe  wraz ze wszystkimi potomkami (w zależności od przyjętej strategii). Następnie do rodzica zależy, co należy zrobić. Możliwe strategie to:

  • Węzeł podrzędny zostanie wznowiony
  • Węzeł podręczny zostanie zatrzymany
  • Węzeł podręczny zostanie zrestartowany
  • Rodzic może nie wiedzieć jak obsłużyć dany błąd. Z tego względu jest możliwość eskalacji problemu do kolejnego rodzica (czyli dziadka względem węzła, który wywołał problem).

Dobór odpowiedniej strategi jest dość trudny i należy wiedzieć czym się charakteryzuje każda z nich.

W przypadku wznowienia działania, stan wewnętrzny aktora jest utrzymany. Ponadto wszystkie dzieci danego węzła również zostaną wznowione.

Dla odmiany restart aktora powoduje usunięcie całego stanu.  Jeśli wiemy, że stan aktora jest błędny (niespójny), wtedy taka strategia jest rozsądna. Analogicznie potomkowie również zostaną zrestartowani.

Przez stan wewnętrzny mam na myśli prywatne pola i zmienne. Kolejka z wiadomościami asynchronicznymi jest przechowywana gdzieś indziej. Jeśli zatem wiadomość “A” wywołała problem i zrestartowaliśmy aktora, wtedy kolejne wiadomości  np. “B”, które były już w kolejce, nie zostaną utracone. Pominiemy wyłącznie wiadomość, która wywołała błąd.

Analogicznie sytuacja wygląda z zatrzymaniem aktora. Jest to skrajna sytuacja, kiedy nie wiemy jak naprawić problem. Wtedy najbezpieczniej jest po prostu zatrzymać węzeł powodujący problem, wraz ze wszystkimi jego potomkami.

Zasadę eskalacji błędu do rodzica, myślę, że można już wywnioskować ponieważ wygląda analogicznie. Węzeł jest wstrzymany tymczasowo w momencie przekazania kontroli do kolejnego rodzica.

Implementacja obsługi błędów w AKKA.NET sprowadza się do tzw.  “Supervision strategy”.

Pierwsza, domyślna strategia to OneForOneStrategy. Oznacza to, że akacja zostanie podjęta wyłącznie na węźle, który spowodował problem. Załóżmy, że mamy rodzica P z dziećmi A, B, C. Jeśli wyjątek został wyrzucony przez “A”, wszelkie akcje (takie jak zatrzymanie, restart) zostaną podjęte na tylko A.

AllForOneStrategy z kolei podejmie akcje dla każdego z rodzeństwa. Oznacza to, że jeśli A wywoła wyjątek to również B,C zostaną np. wstrzymane lub zrestartowane.

Powyższe strategie również definiują jaki wyjątek jaką akcję powinien spowodować (stop, restart, escalate, resume).

W praktyce wygląda to tak, że aktor zwraca swoją strategie, a strategia z kolei przyjmuje kilka parametrów określających możliwe akcje w zależności od wyjątku, liczby powtórzeń itp. W przyszłym wpisie, pokaże jak to wygląda od strony C#.

ASP.NET MVC: Wywoływanie funkcji JavaScript z parametrami z modelu

Czasami w widoku wywołujemy funkcję JavaScript z parametrami, które są przekazane za pomocą ViewModel z kontrolera. Załóżmy, że nasza metoda w kontrolerze wygląda następująco:

       public ActionResult Index()
       {
            return View(new FooModel("Hello World!"));
        }

ViewModel z kolei zawiera jedną właściwość:

    public class FooModel
    {
        public string Text { get; set; }

        public FooModel(string text)
        {
            Text = text;
        }
    }

Następnie zdefiniujmy jakąkolwiek funkcję JS przyjmującą parametry:

<script type="text/javascript">
    function doSomethingInJs(text) {
        $('#sampleContainer').html(text);
    }
</script>

Błędne wywołanie funkcji w razor wygląda z kolei następująco:

 
   <script type="text/javascript">
        doSomethingInJs('@Model.Text');
    </script>

Jest to nic innego, jak klasyczny przykład wstrzyknięcia złośliwego kodu. Dotyczy to wszystkich pól typu string. Nawet jeśli spodziewamy się tylko imienia czy nazwiska, nigdy nie wiadomo jakie dane zawiera view model. Zwykle dane pochodzą z bazy danych. Co jeśli w jakiś sposób, baza danych zawiera już złośliwy kod? Aplikacja powinna traktować wszystkie dane, jako potencjalne zagrożenie.

Co jeśli, następująca treść zostanie przekazana?

    public ActionResult Index()
    {
            return View(new FooModel("<script>alert('Hello World')</script>"));
    }

Na szczęście powyższy kod jest bezpieczny, ponieważ Razor zamieni <script> na znaczniki HTML:

    <script type="text/javascript">
        $(function() {
           doSomethingInJs('&lt;script&gt;alert(&#39;Hello World&#39;)&lt;/script&gt;');
        });
    </script>

Innymi słowy, kod zostanie wyświetlony jak zwykły tekst. Za każdym razem, jak wywołujemy @Model, zawartość jest enkodowana. Co jednak stanie się, jeśli treść zostanie przekazana jako ASCII HEX?

public ActionResult Index()
{
   var text =       "\\x3c\\x73\\x63\\x72\\x69\\x70\\x74\\x3e\\x61\\x6c\\x65\\x72\\x74\\x28\\x27\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64\\x27\\x29\\x3c\\x2f\\x73\\x63\\x72\\x69\\x70\\x74\\x3e";

   return View(new FooModel(text));
}

Razor nie zakoduje treści ponieważ jest to zwykły tekst (brak znaczników HTML). Javascript z kolei, rozpoznaje ASCII w postaci hex (za pomocą \x). Efekt będzie taki, że kod javascript wykona się i wyświetli alert. W tej chwili, jeśli zajrzymy do źródła strony, zobaczymy:

 <script type="text/javascript">
        $(function() {
           doSomethingInJs('\x3c\x73\x63\x72\x69\x70\x74\x3e\x61\x6c\x65\x72\x74\x28\x27\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64\x27\x29\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e');
        });
  </script>

Rozwiązaniem jest endkodowanie parametrów funkcji JavaScript za pomocą HttpUtility.JavaScriptStringEncode:

    <script type="text/javascript">
        $(function() {
           doSomethingInJs('@HttpUtility.JavaScriptStringEncode(Model.Text)');
        });
    </script>

Efektem będzie:

    <script type="text/javascript">
        $(function() {           
doSomethingInJs('\\x3c\\x73\\x63\\x72\\x69\\x70\\x74\\x3e\\x61\\x6c\\x65\\x72\\x74\\x28\\x27\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64\\x27\\x29\\x3c\\x2f\\x73\\x63\\x72\\x69\\x70\\x74\\x3e');
        });
    </script>

Widzimy, że każdy kod ascii, zostały poprzedzony dodatkowym znakiem ‘\’.

Generalnie połączenie jQuery + mieszanie C# z JavaScript jest nie tylko złą praktyką z punktu widzenia bezpieczeństwa, ale również nie jest to zbyt czysty kod. Nie powinno mieszać się kodu inline z szablonami razor. Te dwie warstwy powinny rozwijać się niezależnie. Da nam to nie tylko łatwiejszy w utrzymaniu kod, ale również jak widać powyżej, łatwiej będzie uniknąć luk w bezpieczeństwie.

AKKA.NET: Hierarchia aktorów

Hierarchia aktorów to kolejny bardzo ważny element w projektowaniu systemów rozproszonych, bazujących na aktorach.

Przede wszystkim pomaga w tworzeniu atomowych aktorów. Dzięki hierarchii, pojedynczy aktor może zawierać na tyle mało logiki, że jesteśmy w stanie wykonać operację wielowątkową bez użycia blokad.

Z poprzedniego artykułu o Reactive Manifesto, pamiętamy, że obsługa błędów (resilent) oraz skalowalność muszą być zagwarantowane w systemach reaktywnych. Dzięki hierarchii aktorów bardzo łatwo możemy izolować awarie pojedynczego węzła od innych. Ponadto, ze względu na relacje między różnymi aktorami, możemy dobrać odpowiednio strategie obsługi błędów.

Hierarchia aktorów stanowi drzewo relacji. Każdy węzeł ma rodzica, który stanowi jego zarządcę. To od niego należy, czy w momencie wystąpienia błędu, wszystkie węzły potomne zostaną anulowane czy tymczasowo zawieszone.

Zasada jest taka więc, że tworzymy coraz to nowe poziomy w drzewie aktorów, aż w do momentu, gdy logika w pojedynczym aktorze jest wystarczająco prosta i odizolowana.

W systemie AKKA.NET każdy węzeł ma zarządce i każdy z nich może być również zarządcą swoich potomków.  W AKKA.NET istnieją również systemowe węzły:

root

Aktor / jest bazowym węzeł dla całego systemu. To od niego zaczyna się hierarchia. Później mamy /user oraz /system. Każdy aktor zdefiniowany przez nas, będzie podlegał /user. Jeśli /user zdecyduje się na anulowanie podrzędnych węzłów, nasi aktorzy zostaną anulowani. Z kolei /system, to inny bazowy węzeł, dla celów czysto systemowych takich jak obsługa błędów czy wykonywanie logów.

Załóżmy, że mamy system składający się z dowolnej liczby użytkowników. Nie ma w tej chwili znaczenia co to dokładnie za system. Wiemy, że jednym z aktorów będzie po prostu “ApplicationUser”.  Jednym z rozwiązań, byłoby stworzenie następującej hierarchii:

h

Powyższa hierarchia, składająca się z pojedynczego aktora nie jest zbyt dobra.  Lepiej wprowadzić koncept ApplicationUserController, który będzie przechowywał użytkowników oraz aktorów. ApplicationUserController będzie zatem zarządzał aktorami ApplicationUser:

controller

W momencie zalogowania się nowego użytkownika, kolejny ApplicationUser jest tworzony przez ApplicationUserController.

Przejdźmy zatem do implementacji. Zaczniemy od ApplicationUser:

  public class ApplicationUserActor : UntypedActor
    {
        private readonly string _userName;

        public ApplicationUserActor(string userName)
        {
            _userName = userName;
        }

        protected override void OnReceive(object message)
        {

            Console.WriteLine("Received by {0}: {1}", _userName, message);
        }

        protected override void PreStart()
        {
            Console.WriteLine("{0}: PreStart",_userName);
            base.PreStart();
        }

        protected override void PostStop()
        {
            Console.WriteLine("{0}: PostStop", _userName);
            base.PostStop();
        }

        protected override void PreRestart(Exception reason, object message)
        {
            Console.WriteLine("{0}: PreRestart", _userName);
            base.PreRestart(reason, message);
        }

        protected override void PostRestart(Exception reason)
        {
            Console.WriteLine("{0}: PostRestart", _userName);
            base.PostRestart(reason);
        }
    }

ApplicationUser w naszym przypadku będzie po prostu wyświetlał przychodzące wiadomości. Ponadto przeciążyłem hook’i opisane w poprzednim poście. Ułatwią one później śledzenie zachowania systemu. Każdy ApplicationUser przechowuje nazwę użytkownika, dzięki której później będziemy w stanie rozróżniać poszczególne instancje aktora.

Kolejny aktor będzie służył do zarządzania ApplicationUser. Często określa się go miastem strażnika (guardian), ponieważ to on jest odpowiedzialny za zarządzanie, jak i obsługę błędów (więcej o tym w następnym wpisie). Kod:

public class ApplicationUserControllerActor : ReceiveActor
    {
        public ApplicationUserControllerActor()
        {
            Receive<AddUser>(msg => AddUser(msg));


        }

        private void AddUser(AddUser msg)
        {
            Console.WriteLine("Requested a new user: {0}", msg.UserName);

            IActorRef newActorRef =
                Context.ActorOf(Props.Create(() => new ApplicationUserActor(msg.UserName)), msg.UserName);

        }

        protected override void PreStart()
        {
            Console.WriteLine("ApplicationUserControllerActor: PreStart");
            base.PreStart();
        }

        protected override void PostStop()
        {
            Console.WriteLine("ApplicationUserControllerActor: PostStop");
            base.PostStop();
        }

        protected override void PreRestart(Exception reason, object message)
        {
            Console.WriteLine("ApplicationUserControllerActor: PreRestart");
            base.PreRestart(reason, message);
        }

        protected override void PostRestart(Exception reason)
        {
            Console.WriteLine("ApplicationUserControllerActor: PostRestart");
            base.PostRestart(reason);
        }
    }

Aktor obsługuje wyłącznie wiadomość AddUser, która wygląda następująco:

    public class AddUser
    {
        public string UserName { get; private set; }

        public AddUser(string userName)
        {
            UserName = userName;
        }
    }

W momencie otrzymania AddUser, dodany jest nowy aktor podrzędny za pomocą:

Context.ActorOf(Props.Create(() => new ApplicationUserActor(msg.UserName)), msg.UserName);

Wywołanie ActorOf w kontekście aktora A, spowoduje utworzenie aktora B, który jest podrzędny względem A. Innymi słowy, ApplicationUserActor podlega ApplicationUserController, który z kolei podlega /user. Jak wiemy z początku wpisu, /user jest systemowym aktorem, który zawsze istnieje. Być może, nie potrzebnie nazywałem swoich aktorów również “User”. W każdym, razie /user jest systemowy i nie ma nic wspólnego z logiką biznesową danej aplikacji. Z kolei ApplicationUser oraz ApplicationUserController zostały stworzone przez nas za pomocą powyższego kodu.

Przejdźmy teraz do testowania. Korzeń systemu tworzymy w znany już sposób:

var system = ActorSystem.Create("FooHierarchySystem");

IActorRef userControllerActor=system.ActorOf<ApplicationUserControllerActor ("ApplicationUserControllerActor");

Następnie wysyłamy dwie wiadomości AddUser, w celu dodania dwóch nowych użytkowników, a co za tym idzie, również dwóch nowych aktorów:

userControllerActor.Tell(new AddUser("Piotr"));
userControllerActor.Tell(new AddUser("Pawel"));

Na ekranie zobaczymy, że najpierw ApplicationUserController zostały stworzony, potem dwóch użytkowników zostało dodanych, a po pewnych czasie hooki OnPreStart dla nowych aktorów zostały wywołane:

1

Następnie, chcemy wysłać wiadomość do nowo utworzonych aktorów:

Console.ReadLine();
Console.WriteLine("Sending a message to ApplicationActor(Piotr)...");
var actor1 = system.ActorSelection("/user/ApplicationUserControllerActor/Piotr");
actor1.Tell("Testing actor 1");

Console.ReadLine();
Console.WriteLine("Sending a message to ApplicationActor(Pawel)...");
var actor2 = system.ActorSelection("/user/ApplicationUserControllerActor/Pawel");
actor2.Tell("Testing actor 2");

Warto zwrócić jak uzyskujemy dostęp do wcześniej już stworzonego aktora:

system.ActorSelection("/user/ApplicationUserControllerActor/Piotr");

Więcej o definiowaniu ścieżek napiszę innym razem. W najprostszej postaci zawierają jednak one serie nazw aktorów. Korzeniem zawsze jest /user. Następnie stworzyliśmy pojedynczą instancję ApplicationUserControllerAcrtor. Ostatnią częścią jest nazwa instancji ApplicationUser. W naszym przypadku, jako nazwę tej instancji podaliśmy nazwę użytkownika. Dla przypomnienia:

                Context.ActorOf(Props.Create(() => new ApplicationUserActor(msg.UserName)), msg.UserName);

Drugi parametr, metody Props.Create to nazwa aktora. Ponadto, jak widzimy, hierarchia aktorów nie pełni roli routing’u wiadomości. Nie musimy wysyłać wiadomości do korzenia systemu. Wystarczy zaznaczyć konkretnego aktora i komunikować się bezpośrednio z nim. Relacje aktorów mają wyłącznie znaczenie dla obsługi błędów (zobaczymy w następnym wpisie), jak i zarządzania czasem życia instancji.

W tej chwili, na ekranie powinniśmy zobaczyć następujące logi:
2

Na koniec, zobaczmy jak aktorzy zachowują się w momencie zamykania systemu:

            Console.ReadLine();
            Console.WriteLine("Requesting the system shutdown.");
            system.Shutdown();
            system.AwaitTermination();

Nie trudno domyślić się, że najpierw zostanie zamknięty ApplicationUserController, a dopiero potem dwie instancje ApplicationUser. Całość kodu do testowania:

 class Program
    {
        static void Main(string[] args)
        {
            var system = ActorSystem.Create("FooHierarchySystem");
            IActorRef userControllerActor =
                system.ActorOf<ApplicationUserControllerActor>("ApplicationUserControllerActor");

            userControllerActor.Tell(new AddUser("Piotr"));
            userControllerActor.Tell(new AddUser("Pawel"));

            Console.ReadLine();
            Console.WriteLine("Sending a message to ApplicationActor(Piotr)...");
            var actor1 = system.ActorSelection("/user/ApplicationUserControllerActor/Piotr");
            actor1.Tell("Testing actor 1");

            Console.ReadLine();
            Console.WriteLine("Sending a message to ApplicationActor(Pawel)...");
            var actor2 = system.ActorSelection("/user/ApplicationUserControllerActor/Pawel");
            actor2.Tell("Testing actor 2");

            Console.ReadLine();
            Console.WriteLine("Requesting the system shutdown.");
            system.Shutdown();
            system.AwaitTermination();

            Console.ReadLine();
        }
    }

AKKA.NET – czas życia aktorów, zdarzenia (hooks)

Dzisiaj zacząłem pisać post o hierarchii aktorów. Jest to bardzo ważny element w celu osiągnięcia skalowalności i dobrej obsługi błędów (np. poprzez izolacje wadliwych aktorów).

W połowie jednak stwierdziłem, że najpierw wypada napisać krótki wpis o zdarzeniach (hooks), jakie możemy zdefiniować w AKKA. Pozwoli nam to potem lepiej zrozumieć przepływ informacji w hierarchiach aktorów.

Każdy aktor,  może znajdować się w następujących etapach:

  • Starting – aktor został dopiero stworzony i nie przetwarza jeszcze wiadomości
  • Receiving – aktor może teraz otrzymywać wiadomości
  • Stopping – zwalnianie zasobów
  • Terminated – aktor nie może już otrzymywać wiadomości, ponadto w tym stanie, nie może zostać już wznowiony.
  • Restarting – aktor aktualnie jest resetowany. Oznacza to, że po restarcie może przejść do “Starting”, a potem do “Receiving”, czyli będzie w stanie ponownie przetwarzać wiadomości.

Z perspektywy kodu, poszczególne stany można obserwować, przeciążając odpowiednie metody:

  • OnStart  (Starting) – metoda wywołana przed rozpoczęciem otrzymywania jakichkolwiek wiadomości
  • PostStop (Stopping) – zwalnianie zasobów
  • PreRestart – przed rozpoczęciem restartu.
  • PostRestart po zakończeniu restartu, ale jeszcze przed ponownym wywołaniem OnStart.

Kod:

 class FooActor : UntypedActor
    {
        protected override void OnReceive(object message)
        {
            Console.WriteLine("Received: {0}", message);
        }

        protected override void PreStart()
        {
            Console.WriteLine("PreStart");
            base.PreStart();
        }

        protected override void PostStop()
        {
            Console.WriteLine("PostStop");
            base.PostStop();
        }

        protected override void PreRestart(Exception reason, object message)
        {
            Console.WriteLine("PreRestart");
            base.PreRestart(reason, message);
        }

        protected override void PostRestart(Exception reason)
        {
            Console.WriteLine("PostRestart");
            base.PostRestart(reason);
        }
    }

W celu przetestowania możemy:

            var system = ActorSystem.Create("system");
            var actor = system.ActorOf<FooActor>();

            actor.Tell("Hello World!");
            system.Shutdown();
            system.AwaitTermination();

Poszczególne metody, przydadzą się w następnym wpisie, poświęconym hierarchii aktorów oraz relacjami między aktorami. Każdy z aktorów może być zarządcą więc śledzenie czasu życia aktorów jest pomocne w zrozumieniu tych zasad.