29
May

DI: punkt wyjścia

Ten post jest częścią cyklu o Dependency Injection.


W tym odcinku skupiam się na stanie aplikacji przed jakimikolwiek procesami “upiększającymi”. Stan ten można uzyskać wykonując

git checkout demo1

na podlinkowanym w poprzednim poście repo. Albo można podglądać sobie online: https://github.com/maniserowicz/di-talk/tree/demo1.

Aplikacja, którą mamy upiększyć, została do celów demonstracyjnych zbudowana dość nietypowo. Nie jest to web app, nie jest to nawet console app. Jest to jedna zwykła dllka, ale…

Nie chcemy wiązać się tutaj z żadnym konkretnym frameworkiem. Zamiast tego zasymulujemy dowolny typ aplikacji, który “obsługuje żądania”. Czyli tak naprawdę każdą aplikację. Może to być aplikacja webowa: obsługuje żądania z przeglądarki. Może to być serwis: obsługuje żądania z innych aplikacji. Może to być nawet coś desktopowego, gdzie żądaniem jest akcja użytkownika.

Sercem systemu jest “symulator” dowolnej implementacji “infrastruktury” oferowanej przez jakikolwiek framework do tworzenia aplikacji. Nazwałem to “WebServer” i wygląda tak:

public class WebServer
{
    public void RegisterUser(string email)
    {
        var controller = new UsersController();
        controller.RegisterUser(email);
    }
}

(https://github.com/maniserowicz/di-talk/blob/demo1/src/app/WebServer.cs)

Jak widać, jest to część odpowiedzialna za przyjęcie żądania “skądś”. Akcja, jaką ktoś może wywołać z zewnątrz, to rejestracja nowego użytkownika, a właściwie jego maila. Nasz WebServer musi wykonać dwie czynności:

  1. powołać do życia kawałek aplikacji potrafiący faktycznie zrealizować żądanie (tutaj: UsersController)
  2. wywołać na nim odpowiednią metodę (tutaj: RegisterUser()), przekazując otrzymane parametry

Punkt trzeci byłby zwróceniem rezultatu, ale w tym przypadku to pomijamy.

Proces rejestracji użytkownika składał się będzie z paru dość standardowych kroków:

  1. sprawdzamy czy podany adres e-mail jest poprawny
  2. sprawdzamy czy podany adres e-mail nie jest już zajęty w systemie
  3. tworzymy obiekt reprezentujący nowego użytkownika z podanym adresem oraz unikatowym “tokenem” pozwalającym na weryfikację, czy inicjator akcji jest faktycznie właścicielem konta e-mail
  4. wrzucamy użytkownika do bazy
  5. generujemy “link aktywacyjny” zawierający podany adres e-mail oraz przypisany wcześniej użytkownikowi token
  6. wysyłamy mail z linkiem aktywacyjnym

Standard, prawda? Wszystkie te czynności są zrealizowane w… a jakże, UsersController!

public class UsersController
{
    const string EMAIL_REGEX = @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}";

    public void RegisterUser(string email)
    {
        // check if email is valid
        if (Regex.IsMatch(email, EMAIL_REGEX) == false)
        {
            throw new ArgumentException("Invalid email address");
        }

        // check if email is not taken
        if (UsersDatabase.IsEmailTaken(email))
        {
            throw new InvalidOperationException("Email already taken");
        }

        // create new user
        var newUser = new User
        {
            Email = email,
            RegistrationToken = Guid.NewGuid().ToString(),
        };

        // insert user
        UsersDatabase.InsertUser(newUser);

        // generate activation link
        string registrationLink = string.Format(
            "http://myapp.com/confirm?email={0}&token={1}"
            , newUser.Email, newUser.RegistrationToken
        );

        EmailService.RegistrationEmail(newUser.Email, registrationLink);
    }
}

(https://github.com/maniserowicz/di-talk/blob/demo1/src/app/UsersController.cs)

BTW wykorzystane tutaj klasy nie posiadają implementacji – póki co służą tylko jako “coś do wywołania”.

Przyznajcie się, ile razy w życiu widzieliście taki kod? Ba, może nawet taki pisaliście?

Ja się przyznaję: i widziałem, i pisałem. Buuuuu, żal i płacz jak nic. No ale trudno, nie dość że każdy rodzi się głupi, a potem uczy się całe życie, to większość i tak głupia umiera. Natura.

Taki kod (a należy pamiętać, że jest to dość prosty przypadek klasy z jedną tylko metodą) woła o pomstę do nieba. Źle się to czyta, więc źle się to będzie utrzymywać. Strach czegokolwiek ruszać, bo nie da się do tego napisać testów. Ba, ciężko byłoby to nawet przetestować ręcznie! Jakakolwiek modyfikacja owego potwora prawdopodobnie wiązałaby się ze składaniem ofiar całopalnych i niejedną przelaną łzą.

A co jeśli oprócz maila chciałbym wysyłać też SMSa?

A co jeśli chciałbym sprawdzić czy każda z niezależnych od wszystkiego innego akcji (wygenerowanie linka, walidacja maila, wygenerowanie treści wysyłanej wiadomości…) działa jak powinna bez nieustannego debuggowania i klikania?

A co jeśli chciałbym mieć uczucie zbliżone do pewności, że wszystkie te działania ze sobą współgrają, czyli że e-mail nie zostanie wysłany na niewalidujący się adres albo że user zostanie dodany do bazy tylko jeśli w systemie nie ma już zarejestrowanego takiego adresu?

A co jeśli chciałbym dodać bardziej skomplikowaną walidację adresu e-mail niż tylko poprzez głupi regex?

A co jeśli chciałbym uzyskać transakcyjność na poziomie infrastruktury, coby użytkownik nie został w bazie jeśli nie powiedzie się wysyłka wiadomości, przez co nigdy nie mógłby zweryfikować adresu e-mail, a więc aktywować konta?

A co jeśli… popuszczę wodze fantazji i wymyślę kolejnych kilkanaście potencjalnych scenariuszy? Nic, nudno będzie, więc na tym poprzestanę.

Zróbmy coś z tym…

Autor

Maciej Aniserowicz

Maciej Aniserowicz
"Procent"
developer / architect

MVP
MCP

Search
Facebook
Twitter
Archiwum
Kategorie
© Copyright 2008-2014 Maciej Aniserowicz. All rights reserved. Running on WordPress.