Jak stworzyć udany system informatyczny
-
Upload
qbeuek -
Category
Technology
-
view
656 -
download
2
description
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