Jak stworzyć udany system informatyczny

Post on 26-Jun-2015

656 views 2 download

description

Prezentacja z IT Academic Days 2012 w Białymstoku.

Transcript of Jak stworzyć udany system informatyczny

Jak stworzyć udany system informatyczny?

Jakub Wójciak

Agenda

Słowo wstępu

Błędy

Choroby programistów

Bezpieczeństwo

Build

Drobnostki

Udany system informatyczny?

Marketing

Znany produkt z wieloma użytkownikami

CFO

Zwrócił koszty i zarabia na siebie

Programiści

Łatwość wprowadzania zmian

Szybkie zrozumienie kodu

Administratorzy

Monitoring i diagnozowanie

Skalowanie

Założenia

Aplikacja Web

ASP.NET MVC

C#

.NET 4.5

Visual Studio 2012

SQL Server 2012

SVN

Oczywiste oczywistości

Kod źródłowy w repozytorium

Jakaś architektura

Standardy nazewnictwa, kodowania

Może są testy

Może jest dokumentacja

Błędy

Obsługa błędów – grzechy

Zawiłe konstrukcje try/catch

Łapanie każdego możliwego wyjątku

Logika biznesowa oparta na łapaniu wyjątków

Logowanie rozsiane w kodzie aplikacji

Połykanie wyjątków

Prezentowanie szczegółów każdego wyjątku użytkownikowi

Nie pokazywanie wyjątków w ogóle

Wyjątki – poprawnie

Zasadniczo trzy klasy wyjątków

LogicException

BusinessException

ChainedException

Dodatkowe wyjątki po to, aby na szczególne sytuacje zareagować w szczególny

sposób

Dlatego jest ich zwykle bardzo mało, pojedyncze klasy

Jedno miejsce, gdzie obsługiwane i logowane są wszystkie wyjątki

Czasami obsługa to zalogowanie błędu

Czasami obsługa to zaprezentowanie błędu użytkownikowi

Łatwy dostęp do metody LogException z każdego miejsca kodu

LogicException

LogicException(string format, params object[] values)

Wykryłeś błąd techniczny, który nie mógł wystąpić z winy użytkownika i na który

użytkownik nic nie poradzi

Czyli błąd innego programisty!

Treść wyjątku może być w dowolnym, jednym języku i może zawierać słowa

niecenzuralne ;-)

Wyjątek ze stack trace trafia do logu, nigdy nie jest prezentowany użytkownikowi

Ten log monitorujesz często i znalezione wyjątki naprawiasz!

BusinessException

BusinessException(string format, params object[] values)

Wykryłeś błąd logiczny, który ma być zaprezentowany użytkownikowi i na który

użytkownik może coś zaradzić

Treść rzucanego wyjątku jest z zasobów, w języku użytkownika

Treść prezentujesz użytkownikowi, wyjątek logujesz, ale do osobnego dziennika

Ten log monitorujesz i zastanawiasz się, jak ułatwić pracę użytkownikom

ChainedException

ChainedException(Exception innerException, string format, params object[] values)

Złapałeś wyjątek i rzucasz go dalej z dodatkowymi informacjami

Czyli błąd jest gdzieś wewnątrz, ty tylko zostawiasz ślad co się wykonywało

Podobnie jak LogicException:

Treść wyjątku może być w dowolnym, jednym języku i może zawierać słowa

niecenzuralne ;-)

Wyjątek ze stack trace trafia do logu, nigdy nie jest prezentowany użytkownikowi

Logowanie błędów

Dzwoni klient i mówi:

„Ale macie zepsuty system, za każdym razem jak kliknę w ten guzik, dostaję inny

błąd!”

„Wchodzę do systemu i nic nie widzę!”

Ze względów bezpieczeństwa nie prezentujemy szczegółów wyjątku użytkownikowi

(stack trace szczególnie zakazany!)

Logujemy więc błędy na serwerze i każdemu błędowi w dzienniku nadajemy numerek

Użytkownikowi prezentujemy komunikat „Wystąpił błąd {numerek}. Administrator

został o tym fakcie powiadomiony.”

Jaki numerek nadać błędowi?

Kolejny sekwencyjny?

Użytkownik zobaczy jak bardzo zepsuty jest nasz system ;-)

Losowy (random)? Unikatowy(guid)?

„Za każdym razem mam inny błąd, wasz system jest nic-nie-warty”

Numerek unikatowy dla każdego rodzaju błędu!

Policz sumę kontrolną ze stack trace (MD5 / SHA1)

Z fragmentu sumy kontrolnej zrób numer błędu

„Wystąpił błąd 154-776-923. Administrator został o tym powiadomiony.”

Błędy w JavaScript

Błędy występują nie tylko na serwerze

Zdarza się, że połowa kodu aplikacji jest po stronie użytkownika, w przeglądarce

Błąd JavaScript może mieć wyjątkowo wredne skutki

Klikam w przycisk i nic się nie dzieje

Kręcidełko AJAX kręci się i kręci i kręci• Ale ten wasz system jest wolny!

Co zrobić z JavaScript?

Jedno wspólne miejsce łapania i logowania błędów JavaScript w przeglądarce

window.onerror

$.ajax.onerror

Po złapaniu:

Wysłać pod specjalny adres na serwerze• adres bieżącej strony,• szczegóły błędu,• informacje o przeglądarce (co najmniej agent-string)

Po stronie serwera zalogować do specjalnego dziennika

Wszystko albo nic

Większość operacji na serwerze może być w pełni transakcyjna

Jeśli operacja jest read-only

Jeśli modyfikacje są robione, ale tylko w lokalnej bazie danych

Z defaulta warto:

rozpoczynać transakcję na początku requestu HTTP,

komitować po udanym zakończeniu requestu HTTP,

rollbackować w przypadku wyjątku,

Bardziej precyzyjne sterowanie transakcją potrzebne gdy są inne efekty uboczne

operacji:

komunikacja z zewnętrznym systemem,

nieodwracalne zmiany poza transakcyjną bazą danych,

wysłano maila,

Choroby programistów

Choroby programistów

WOMM

Works on my machine!

NIH

Not Invented Here

Architekturoza

WOMM

Musi istnieć łatwo dostępne, wspólne środowisko testowe

Najlepiej codziennie buildy

Continous Integration?

Jeśli nie działa, ale WOMM, to spraw, aby zadziałało na środowisku testowym

Koniecznie podaj potem zdiagnozowaną przyczynę problemu!

NIH

„Musimy napisać własny kod rozmawiający z WebService’em, bo WCF jest zepsuty”

„Musimy mieć własny framework MVC, ten od Microsoftu jest niedobry”

„Musimy mieć własną klasę String, ta w .NET jest poroniona”

NIH jest podstępny:

Z jednej strony nasza dziedzina jeszcze raczkuje, co widać po dynamicznym

rozwoju• Nie wszystkie „oficjalne” rozwiązania spełniają dziś nasze wymagania• Czasami naprawdę trzeba coś zrobić samemu lepiej

Z drugiej strony wybranie ścieżki NIH skazuje na nią do końca projektu, nawet jeśli

„oficjalna” technologia nas dogoni

Decyzja o NIH musi zapaść za zgodą wszystkich udziałowców projektu: PM, właściciel

biznesowy, główni deweloperzy

Architekturoza

Krzywa olśnienia architekturowego

Czas

Złoż

onoś

ć ko

du

Jak rozpoznać architekturozę

Ile kodu faktycznie wykonuje jakieś operacje, a ile stanowi przelotki, interfejsy,

abstrakcje, adaptery i warstwy pośrednie?

Czy interfejsy mają zwykle tylko jedną implementację?

Czy implementacje interfejsów są faktycznie wymienne ze sobą?

Czy masz dużo kodu, który kopiuje strukturę w strukturę?

Czy twoja prosta aplikacja składa się z rozproszonych serwisów hostowanych na

osobnych serwerach?

Czy musisz stosować transakcje rozproszone?

Przykład architekturozy

Model danych zbudowany w Entity Framework (klasy POCO generowane z edmx)

Każda z klas modelu danych implementuje ręcznie napisany interfejs, potwtarzający

jeszcze raz wszystkie pola z modelu

Dostęp do danych wyłącznie za pośrednictwem klas-repozytoriów, które:

parametry castują z interfejsów do klas modelu danych

wyniki kastują z klas modelu danych do interfejsów

każde repozytorium implementuje swój interfejs – jest jego jedynym

implementatorem

Klasy serwisów z logiką biznesową:

każdy serwis implementuje swój interfejs – jest jego jedynym implementatorem

parametry i wyniki metod serwisów (struktury danych) są opisane interfejsami,

które posiadają dokładnie jedną implementację

Tylko spokój może nas uratować

Każdy interfejs musi być uzasadniony:

faktycznie będzie kilka jego implementacji,

na potrzeby testów jednostkowych będziesz dostarczał mockup inną jego

implementację,

Inversion of Control / Dependency Injection wcale nie wymaga interfejsów!

Korzystasz z Entity Framework? LinqToSql?

To jest twoja implementacja repozytorium, nie twórz kolejnej warstwy!

Niech twoje serwisy wywołują zapytania Linq bezpośrednio na twoim ObjectContext• Mogą je też prekompilować w zmiennych statycznych

Nie ma nic zdrożnego, aby twój widok MVC przy renderowaniu wyciągał dane

bezpośrednio z obiektów modelu EF

Byleby samo pobieranie danych było realizowane przez kontroler + serwis

Czy ten kod jest w ogóle potrzebny?!

Bezpieczeństwo

Bezpieczeństwo

Zbuduj bezpieczeństwo w oparciu o przywileje / prawa

Przywilej / prawo opisane jest czasownikiem opisującym operację:

CanAddNewUser, CanDeleteUsers, CanViewHomeScreen

Przywileje zawsze dodają coś nowego, nigdy nie odbierają:

tzw. addytywny model uprawnień

nie można mieć: CannotBookExpensiveHotel, musi być: CanBookExpensiveHotel

Nie posiadasz przywileju dopóki go nie dostaniesz

Zbiór przywilejów jest stały dla danej wersji systemu

Skąd się biorą przywileje

Możesz przewidzieć zbiór przywilejów dostępny nawet anonimowym, niezalogowanym

użytkownikom:

Przywilej: CanViewLoginScreen, CanSendPasswordReminder

Możesz przewidzieć zbiór przywilejów dostępny każdemu zalogowanemu użytkownikowi:

Przywilej: CanLogoff, CanChangeUserLanguage, CanViewHelp

Możesz przypisywać przywileje bezpośrednio użytkownikom

Lepiej: możesz zgrupować przywileje w role i przypisywać użytkownikom wiele ról

Role można definiować dynamicznie, np. w bazie danych

Nazwa roli mówi o tym, kim użytkownik będzie w systemie:• DepartmentAccountant• GeneralAccountant• TechnicalAdministrator – może wszystko!• UsersAdministrator

Mogę coś zrobić, jeśli którakolwiek z moich ról posiada dany przywilej

Role specjalne:• ANONYMOUS• EVERYONE

Rodzaje przywilejów

Osobny przywilej dla każdego punktu wejścia do systemu:

Każda strona aspx

Każda akcja każdego kontrolera

Każda metoda każdego WebService’u

Sprawdzanie takich przywilejów jest automatyczne i nie do ominięcia, w jednym

miejscu kodu

Osobny przywilej do czynności biznesowych:

Każdy rodzaj encji ma swój zbiór przywilejów

Często wystarczy prawo do oglądania i prawo do modyfikacji:• CanViewInvoices• CanManageInvoices (dodawanie, modyfikacja, usuwanie)

Sprawdzanie takich przywilejów jest explicite w kodzie, dba o to programista:• W kontrolerze brak uprawnień to „miękki” komunikat błędu• W serwisie brak uprawnień to krytyczny wyjątek

Build

Przychodzi nowy programista...

Na wejściu otrzymuje:

Komputer z zainstalowanym oprogramowaniem (Windows, IIS, Visual Studio, SQL

Server Developer, klient SVN)

Namiary na repozytorium kodu źródłowego (url, login, hasło)

Namiary na swoje pierwsze zadanie w postaci ticketu w systemie zarządzania

projektem

Po ilu minutach ma uruchomiony na swoim komputerze kompletny system i może

realizować zadanie?

Ile kroków musiał wykonać?

Tylko dwa kroki

Pobierz kod źródłowy z repozytorium

Wykonaj „Build solution” w Visual Studio

Voila!

Bonus: odtworzenie lokalnie bazy produkcyjnej to wykonanie pojedynczego

polecenia

Jak to osiągnąć?

Kompletne kody źródłowe w repozytorium

Biblioteki zależne podpięte np. przez NuGet

Baza danych wersjonowana w repozytorium

Wersjonowanie bazy danych

Baza danych musi być wersjonowana!

Musi!

Każdy deweloper pracuje u siebie na lokalnej kopii bazy danych

Repozytorium zawiera kolejno numerowane skrypty różnicowe SQL

W bazie jest tabelka przechowująca numery już wykonanych skryptów

Podczas build zawsze uruchamia się narzędzie, które łączy się z bazą i sprawdza, czy

są skrypty do wykonania

Nasze rozwiązanie

Skrypty różnicowe:

0001.sql, 0002.sql, 0003.sql itd

Pierwszy skrypt zakłada, że baza jest świeżo stworzona

Kolejne opierają się na stanie bazy po wykonaniu poprzednich skryptów

Wersjonujemy: tabele, indeksy, dane słownikowe

Każdy skrypt jest transakcyjny (wszystko albo nic)

Skrypty obiektowe:

ProcedureCleanHotels.sql, TriggerAfterInsertReservation.sql

Obiekty łatwo tworzone oraz zależne od tabel

Funkcje, procedury, widoki

Nasze rozwiązanie

Połącz się z bazą

Jeśli nie istnieje, to stwórz albo zakończ się błędem

Pobierz z bazy listę wykonanych skryptów

Pobierz z dysku listę wszystkich dostępnych skryptów

Jeśli są na dysku skrypty, których nie ma jeszcze w bazie:

Uruchom procedurę DropObjects.sql, która kasuje wszystkie widoki, funkcje i

procedury

Wykonaj po kolei wg numerów plików wszystkie skrypty różnicowe, których

jeszcze w bazie nie ma• Każdy pomyślnie wykonany plik „odhacz” w bazie• Każdy błąd powoduje od razu stop całego procesu

Wykonaj wszystkie skrypty obiektowe, alfabetycznie wg nazw plików

Automatyczne migracje

Obecne frameworki potrafią automatycznie porównać schemat bazy ze schematem

logicznym np. w edmx i automatycznie zmodyfikować bazę

Należy traktować to jako rozwiązanie ułatwiające pisanie skryptów różnicowych, a nie

jako docelowy sposób wgrywania wersji na produkcję

Treść każdego skryptu musi być widoczna gołym okiem jako SQL, a skrypt musi być

opatrzony loginem programisty, który się pod nim „podpisał”

Jak ktoś coś zepsuje, to nie może się wykręcać WOMM

Inni członkowie zespołu natychmiast egzekwują poprawkę od winnego

Drobnostki

Data i czas

Różne rodzaje czasu i daty

Punkt w czasie: data i czas, zawsze operuj i przechowuj jako UTC

DateTime.UtcNow

Wprowadzone przez użytkownika w jego strefie lokalnej, kowertuj od razu do UTC

Przed pokazaniem użytkownikowi, kowertuj z UTC na strefę lokalną

Użytkownik może sobie zmienić strefę czasową

Sama data, np. data ważności dokumentu

Przechowuj z obciętym czasem: date.Date

Porównuj z datą i czasem w bieżącej strefie czasowej użytkownika

Ten sam dokument w tej samej chwili może być w różnych częściach świata

jeszcze ważny i już nieważny!

Sam czas

Tekst

Praca z tekstem, zawsze w Unicode

Wczytując tekst nie-Unicode daj wybrać kodowanie źródłowe lub umożliw

odpowiednią konfigurację

Zapisując tekst, wybieraj format Unicode

Jeśli nie może być Unicode, daj wybrać kodowanie docelowe

Liczby losowe

Random

Pseudolosowy, inicjalizowany z seed

Korzystaj z jednej, mutowanej instancji

Synchronizuj kod wielowątkowy

Guid

Unikatowy, nie losowy!

Guid można zgadnąć!

Kolejny Guid może po prostu różnić się jedną cyfrą!

RandomNumberGenerator

prawdziwe źródło bezpiecznej losowości

Refleksja

Bardzo przydatne narzędzie do meta-programowania

Niestety, powolne

Unikaj używania refleksji bezpośrednio w kodzie

Zbuduj sobie helpery do refleksji

Zaimplementuj po prostu z użyciem refleksji

Potem pomierz wydajność

Potem zoptymalizuj w jedym miejscu

Szybszy lecz bardziej skomplikowany mechanizm: Expression<T>

Linq

Linq jest potężne!

zapytanie możesz prekompilować

zapytania możesz składać

zapytania możesz budować zupełnie dynamicznie

zapytania możesz transformować

zapytania możesz analizować

SMT Software S.A.ul. Piłsudskiego 1350-048 Wrocławtel. +48 71 769 59 00fax +48 71 769 59 01www.smtsoftware.com

Dziękujemy za uwagę i zapraszamy do współpracy

Wrocław Warszawa Gliwice Poznań Katowice Białystok Utrecht (Holandia)

Jakub Wójciak

jakub.wojciak@smtsoftware.com