Stałe ID obiektu w API przy zmieniającym się ID w bazie danych

Podczas tworzenia Star Trek API szybko doszedłem do wniosku, że nie mogę ujawniać klientom API ID-ków bazodanowych. Powodem było to, że ID-ki te nieustannie się zmieniały. Ilekroć robiłem zasilenie od nowa, było niemal pewne, że dostanę nieco inne zestaw stron, które zostaną przetworzone w innej, niż poprzednio, kolejności. Wydało mi się oczywiste, że gdybym sam chciał skorzystać z danych tego typu, jak udostępniane przez moje API, oczekiwałbym, że ID będą stałe. Chciałbym je gdzieś zapisywać i posługiwać się nimi w przyszłej komunikacji z API.

Zadanie

Trzeba było wymyślić sposób na to, aby generować stałe, lub przynajmniej w ogromnej większości przypadków stałe identyfikatory dla wszystkich encji. Dodatkowym bonusem byłoby to, aby identyfikatory były unikalne w obrębie całego API.

Wykonanie

Memory Alpha i Memory Beta mają własną numerację stron, którą na szczęście ujawniają. Każda strona ma ID, który nie zmienia się przez cały cykl jej życia, nawet wówczas, gdy strona zostanie przeniesiona pod inną nazwę. Identyfikatory stron z z wiki mogły więc zostać pierwszą ważną częścią identyfikatora, który już wówczas zacząłem nazywać GUID-em, a któremu ostatnio zmieniłem nazwę na UID, by nie było wątpliwości, że to co innego, niż GUID.

Od razu było jasne, że potrzebuję jeszcze czegoś, co identyfikowałoby encję w unikalny sposób. ID-ki z Memory Alpha i Memory Beta mogły się powtarzać. Do UID-ów dołączyłem więc 2 znaki, które reprezentowały encję, oraz 2 znaki, które reprezentowały źródło. W ten sposób powstał na przykład UID, który wyglądał tak: CLMA0000108985. Dwa pierwsze znaki, CL, oznaczały encję ComicCollection, dwa kolejne, MA, oznaczały, że źródłem jest Memory Alpha. Natomiast 10 cyfr to ID z wiki. Razem zawsze 14 znaków, co wydało mi się wystarczające na API tej wielkości.

Pierwsze, i na razie jedyne, kłopoty

Pierwszy i na razie jedyny problem, który napotkałem, dotyczył bytów, które nie mapowały się na dokładnie jedną stronę z wiki. Chodziło o encje reprezentujące numery ISBN i ASIN. Na jednej stronie mogło występować kilka takich numerów.

Na szczęście numery ISBN mają 10 lub 13 znaków, a numery ASIN – 10 znaków. Łatwo było z nich wygenerować 14-znakowe UID-y. 10-znakowym numer ISBN mają po prostu format ISBN160010603X, 13-znakowe ISBN-y format I9781566199094, a 10-znakowe numery ASIN-y – format ASINB008L4YH4W. Można więc w dalszym ciągu zagwarantować unikalność UID-u w systemie.

Implementacja

Ostatecznie wszystkie te wymagania zostały zaimplementowany w klasie UidGenerator. Klasa jest wołana zawsze, zanim encja zostanie pierwszy raz zapisana, i w ten sposób UID zawsze jest obecny.

UID-y zastępują w API bazodanowe identyfikatory. To nimi pyta się o pojedyncze REST-owe i SOAP-owe obiekty. UID-y też, wraz z nazwami lub tytułami, trafiają do encji nagłówkowych, takich jak MovieHeader. Na ich podstawie, gdy ktoś będzie chciał, będzie mógł pobrać więcej danych na temat interesującej encji.

UidGenerator podczas inicjowania aplikacji sprawdza, czy będzie potrafił wygenerować unikalne identyfikatory dla wszystkich encji. Jeśli nie, trzeba dodatkowo dodać identyfikator skrócony identyfikator encji w statycznej mapie. W nieprawdopodobnym przypadku, gdy ID strony z Memory Alpha lub Memory Beta będzie większy, niż 9999999999, zostanie rzucony wyjątek, którym będzie trzeba się zająć. Ale to temat na odległą przyszłość.

@Unroll w Spocku, przykład zaawansowany

Spock jest frameworkiem do testów, którego popularność w świecie JVM-a jest nie do podważenia.

W internecie można znaleźć wiele tutoriali, które wprowadzają w podstawy adnotacji @Unroll. Ogólnie chodzi w niej o to, że mamy możliwość uruchomienia tego samego kodu z wieloma zestawami danych. Najprostszy przykład użycia @Unroll wygląda tak:

Interesowało mnie, ile jeszcze można wycisnąć z tej adnotacji, i o tym będzie ten post.

Wymagania

Przy pisaniu procesorów, które tworzą encje takie, jakie Localization, potrzebowałem przetestować sytuację, gdy do procesora wchodzi encja reprezentująca stronę z Memory Alpha, posiadająca jakiś zestaw kategorii. Na podstawie obecności tych kategorii były ustawiane konkretne booleanowe flagi w encji wyjściowej, w tym przypadku właśnie Localization. Gdybym chciał to zapisać w kilkunastu czy kilkudziesięciu testach, powtórzyłbym bardzo dużo kodu.

Jednocześnie chciałem sprawdzić w każdej iteracji więcej, niż jedną daną wyjściową, ponieważ obok flagi, która w sytuacji obecności danej kategorii miała być ustawiona na prawdę, chciałem też sprawdzać liczbę nienullowych pól w całej encji wyjściowej.

Trzecim wymaganiem było, by nazwa flagi była przechowywana jako string. To także okazało się możliwe z @Unrollem. Chociaż zapewne to głównie zasługa Grooviego, który pozwala na dostęp do obiektów z użyciem tej samej składni, która umożliwia dostęp do map.

Ostatnim wymaganiem było, by zapisać w metodzie testowej interakcje, najlepiej z wyspecyfikowaną ich ilością. To też się prawie udało.

Implementacja

Ostateczna implementacja wygląda tam:

Opis implemenacji

W bloku given zostały zapisane wszystkie wymagane w każdej iteracji interakcje. Można by też po prostu dostarczyć do metody testowej już zaprogramowane stuby, które miałyby te same interakcje.

W bloku expect pobieramy lokalizację z procesora, a następnie, z użyciem nazwy flagi, którą też mamy w datasecie, porównujemy wartość flagi z kolejną wartością zapisaną w datasecie. Ostatecznie refleksją sprawdzamy ilość nienullowych pól w encji reprezentującej lokalizację.

W bloku where znajdują się dane. Tutaj największą niespodzianką jest to, że można w ramach bloków konstruować obiekty i odwoływać się o innych metod. Patrząc na przykłady w sieci, nie jest to wcale oczywiste.

Ograniczenia

Nie znalazłem sposobu, by wyspecyfikować ilość interakcji w ramach jednej metody testowej z @Unrollem. Interakcji nie można umieszczać w blokach given i expect, a z kolei blok then nie może współegzystować z blokiem expect. Jeśli więc chcemy sobie zaprogramować interakcje, możemy to zrobić tylko w bloku given, ale bez zapisywania przy nich ilości.

Podsumowanie

Umiejętnie użyty @Unroll pozwala napisać dowolny test, z wyjątkiem takiego, który zlicza ilość interakcji.

Kiedy używać @Unrolla, a kiedy jest on overkillem? Myślę, że dobrą granicą są cztery bardzo podobne metody testowe. Jeśli tyle mamy, należałoby je połączyć w jedną metodą z @Unrollem. Ale zgodnie z duchem reguły DRY, nawet 2 podobne metody testowe to nie byłoby za dużo, by zacząć używać @Unrolla.

A co, jeśli mimo wszystko chcemy zliczać interakcje i mieć data tables? Moje doświadczenie pokazuje, że lepiej rozdzielać kod, który głównie operuje na interakcjach z zależnościami, od kodu, który głównie operuje na danych. Jeśli powstaną dwie osobne klasy, albo przynajmniej dwie osobne publiczne metody, z których jedna operuje głównie na zależnościach klasy, a druga głównie przetwarza dane, problem z brakiem zliczania interakcji przy użyciu @Unrolla powinien zostać zminimalizowany.

Throttling API w Springu

Publicznie dostępne API, takie jak Star Trek API, w miarę, jak zyskują popularność, muszą poradzić sobie ze zwiększającym się ruchem. Jedną ze strategii radzenia sobie ze zbyt wieloma zapytaniami jest nałożenie limitu na klienty. Można to zrobić na kilka sposobów, np. implementując limit godzinny lub dobowy dla danego adresu IP, lub odnawialne, płatne lub odświeżane cyklicznie, limity dla kluczy API.

Istniejące implementacje

Spodziewałem się, że ten problem został już rozwiązany i wystarczy dorzucić do projektu zależność, która zajęłaby się throttlingiem. Tak jednak nie jest. Istnieje co prawda Apache CXF Throttling Feature, ale analizując kod doszedłem do wniosku, że nie nadaje się on do throttlingu w sytuacji, gdy mamy wiele klientów. Nadaje się jedynie do throttlingu per zasób, a to mnie nie zadowala. W internecie można znaleźć też posty polecające RateLimiter z Guavy, ale tutaj z kolei filozofia jest taka, że limitujemy dostęp do zasobu, a wątek, który chce zająć zasób, ma czekać, jeśli zasób jest już zajęty. To nie wpisuje się w moją wizję.

Założenia

Postanowiłem zaimplementować omawianą funkcjonalność samodzielnie. Na pierwszy ogień poszedł throttling na podstawie adresu IP. Założenia były proste. Adresy IP, które uzyskują dostęp do API, będą zapisywane w bazie. Pierwszy zapis adresu IP ustawi jego maksymalny limit zapytań na 100, a ilość pozostałych zapytań na 99. O każdej pełnej godzinie limity są odświeżane i każdy adres IP może znów wykonać 100 zapytań. Co godzinę, 30 minut po pełnej godzinie, adresy które nie wykonywały zapytań dłużej, niż dzień, są usuwane.

Model

Postanowiłem nie zamykać się na możliwość, że w przyszłości zaimplementuję także throttling na podstawie kluczy API, a więc schemat bazy danych przygotowałem pod obie te ewentualności. W ten sposób powstał model oraz towarzyszące mu repozytorium z niezbędnymi operacjami.

Interceptory

Skoro było już skąd pobrać i gdzie zapisać informację o tym, czy dany adres IP może dalej wysyłać zapytania do API i otrzymywać poprawne odpowiedzi, trzeba było zaimplementować interceptory, które pozwoliłyby odpowiednio wcześnie zdecydować o tym, że przetrwarzania żądania powinno zostać anulowane. Nie chodzi wszak o to, żeby zapytać bazę, a na koniec zamiast poprawnej odpowiedzi pokazać komunikat o błędzie.

Na początek trzeba było stworzyć interfejs ApiThrottlingInterceptor. Bean implementujący interfejs zostaje zarejestrowany w trakcie tworzenia CXF-owego serwera, oraz we wszysktich endpointach SOAP-owych, w ich wspólnej metodzie wytwórczej. Interfejs będzie implementowany przez dwie klasy, zależnie od tego, czy jest włączony springowy profil odpowiedzialny za throttling, czy nie. Wówczas, gdy profil nie jest obecny, jako bean zostanie zarejestrowana klasa, która nic nie robi. Jeśli profil jest obecny, zostanie zarejestrowana klasa, która deleguje nadchodzące zapytanie do fasady.

Proces walidacji

Zadaniem fasady jest przekazanie nadchodzącego zapytania do walidatora i, jeśli walidator nie pozwoli, by zapytanie było dalej procesowane, rzucenie wyjątku, inego dla REST-owych, a innego dla SOAP-owych zapytań.

Walidator ma na celu pobranie adresu IP z zapytania i sprawdzenie, czy ten adres IP może jeszcze wykonywać zapytania. Jeśli tak, zwraca DTO z flagą throttle ustawioną na fałsz, i pustymi komunikatem o błędzie. Jeśli adres IP nie może już wykonywać zapytać, zwracany jest DTO z flagą throttle ustawioną na prawdę, oraz powodem throttlingu.

Komunikaty o błędach

Jeśli chodzi o SOAP-owy wyjątek, rzucenei SoapFault-a wystarczy, by CXF zamienił go na komunikat w spodziewanym formacie. W przypadku REST-owego błędu trzeba była napisać customowy mapper, który przemapowuje REST-owy wyjątek na format zdefiniowany w swaggerowym kontrakcie.

Procesy cykliczne

Na koniec trzeba było stworzyć dwa procesy cykliczne. Jeden do odświeżania limitów IP, a drugi do kasowania starych wpisów z adresami IP. Oba jako jedyną zależność mają repozytium encji Throttle, i po prostu wykonują na nim wymagane operacje.

Podsumowanie

I to właściwie wszystko. Jedyną podstępną rzeczą, na którą trzeba uważać w implementacji takiej jak ta, to by dekrementować ilość pozostałych zapytań za pomocą SQL, a nie Javy. Może się bowiem zdarzyć, że pobierzemy dla kogoś limit i zechcemy go w następnej operacji zapisać pomniejszony o 1, a w międzyczasie proces cykliczny ponownie ustawi limit na 100. Czyli trzeba zrobić tak.

Myślę, że zaimplementuję w przyszłości także throttling na podstawie kluczy API. Nie robię tego w tej chwili, bo w przeciwieństwie do limitowania dostępu do API na podstawie adresu IP, limitowanie na podstawie kluczy wymaga, by powstał jakoś frontend, do którego można się zalogować, najlepiej GitHubem, Facebookiem i Googlem. Wymagane byłoby tu też, aby każdy zalogowany użytkownik mógł swoimi kluczami zarządzać, czyli przynajmniej tworzyć nowe, inwalidować stare, a być może też requestować większe limity lub je dokupować. To praca, której nie chcę na razie wykonywać, i wolę skupić się na skończeniu samego API.

Statystyki repozytoriów projektów zgłoszonych do DSP

Jeśli chcesz przejść bezpośrednio do statystyk, kliknij tutaj.

Motywacja

Istnieje już strona zbiorcza z postami, gdzie możemy się dowiedzieć, kto i kiedy opublikował ostatni post. Po uruchomieniu w konsoli polecenia $(".panel-heading.text-center.blog-title").length dowiemy się dodatkowo, ile osób opublikowało post w ciągu ostatniego tygodnia. To ciekawa statystyka, ale brakowało mi informacji o tym, jak tak naprawdę idzie pozostałym uczestnikom, jeśli chodzi o projekty, a nie blogi, i – nie ukrywam – jak wypadam na ich tle.

Rezultat

Z początkiem marca, w przerwach od pisania Star Trek API robiłem jeszcze jeden projekt. Jego cel był prosty: korzystając ze zbiorczej strony z tegorocznymi uczestnikami, zebrać linki do wszystkich repozytoriów, ściągnąć te repozytoria, zgromadzić na ich temat statystyki, a następnie opublikować w formie interaktywnego grida. Od kilku dni aplikacja, która to wszystko robi, jest już napisana. Codziennie wieczorem odpalam ją, żeby zebrała statystyki aktualne na poprzedni dzień. Od kilku dni niczego już nie poprawiam, więc myślę, że to dobry moment, by opublikować wyniki. Można je zobaczyć tutaj.

Aktualizacje

Aktualizacje statystyk będę przeprowadzał przez cały czas trwania konkursu. Codziennie wieczorem, a w weekendy w ciągu dnia, będą pojawiały się statystyki aktualne na poprzedni dzień.

Stos technologiczny

Aplikację do zbierania statystyk napisałem w tym samym stosie, w którym piszę Star Trek API: kod produkcyjny powstał w Javie, a testy w Groovym. Aplikacja nie ma bazy danych, i po prostu raz dziennie generuje JSON-a ze wszystkimi statystykami, który jest potem ładowany do ag-Grida. Intensywnie wykorzystywanymi zależnościami backendowymi są też jsoup (do parsowania HTML-a) i EGit (obiektowy wrapper wokół Gita).

Jak zbierane są statystyki

Statystyki zbierane są dla okresu od 1 marca do dnia podanego na stronie statystyk. Przykładowo, dziś, 8 kwietnia przed południem uruchomiłem zasilenie, które zebrało statystyki od 1 marca do 7 kwietnia. Od strony technicznej wygląda to tak, że dla każdego commitu generowany jest diff z poprzednim commitem i zliczana jest ilość zmienionych, dodanych i usuniętych linii. Zliczana jest też ilość commitów. W większości kategorii, poza ilością commitów na dzień, liczby są zokrąglane do integerów, więc mogą istnieć jakieś minimalne rozjazdy (np. ktoś dodaje dziennie 220 linii, zmienia 293 linii, ale usuwa 72). Wierzę, że takie zaokrąglenia nie wpływają na ogólny obraz projektów.

Obserwacje

Oto kilka obserwacji, które poczyniłem do tej pory, przeglądając zgromadzone statystyki:

  • Wiele osób podało niepoprawne linki do repozytoriów. Były to między innymi linki do profili użytkowników na GitHubie.
  • Wiele repozytoriów do tej pory jest pusta.
  • Niektóre z osób, ktore zgłosiły projekty do DSP, miało już napisany wcześniej jakiś kod, ale nie był on wersjonowany, i initial commit był wykonywany na potrzeby DSP.
  • Są osoby, które commitują do repozytium rzeczy, które nie powinny się tam znaleźć, tzn. te, który powinny być sciągnięte jakimś build toolem, w efekcie w statystykach wygląda to tak, jakby commitowały po kilka tysięcy linii dziennie.
  • Pierwsza osoba, jeśli chodzi o ilość wykonanych commitów, pisze 10 razy mniej linii dziennie, niż druga osoba w tej kategorii.
  • Tylko 49 osób wykonuje więcej, niż jeden commit dziennie.
  • Wśród osób, które zrobiły jakieś commity, 18 osób zacommitowało poniżej 2 linii dziennie.
  • Przyszłość

    Zachęcam do samodzielnego oglądania statystyk i do wyciągania wniosków. Jeśli ktoś chciałby się ze mną skontaktować w sprawie statystyk, bo ma na przykład pomysł na dodatkową funkcjonalność, albo nie chce być na liście, może to zrobić mailem, na Slacku lub w komentarzach.

Brakujący feature JPA

Ten post będzie jedynie narzekaniem na niedoskonałość technologii, z którą pracuję nad projektem zgłoszonym do DSP.

Problem

W trakcie tworzenia Star Trek API muszę modelować relacje wiele do wielu w znacznej ilości. Często jest tak, że dwie encje mają między sobą kilka relacji wiele do wielu. Relacje te oznaczają oczywiście co innego. Np. mam filmy, a w nich relacje do ludzi pracujących nad Star Trekiem w różnych rolach. Oddzielnie są producenci, oddzielnie autorzy scenariusz, i tak dalej. W kodzie wygląda to na przykład tak:

Jak widać, relacja między encją Movie i encją Staff występuje więcej niż raz. Trzeba więc utworzyć dwie tabelki pośrednie, movies_screenplay_authors i movies_story_authors, dla obu tych relacji. Jeśli tabelek jest więcej, to trzeba utworzyć więcej relacji. W efekcie w w bazie mam tabelkę dla filmów i relacje filmów z innymi encjami:

movie
movies_characters
movies_directors
movies_performers
movies_producers
movies_screenplay_authors
movies_staff
movies_stand_in_performers
movies_story_authors
movies_stunt_performers
movies_writers

Z czasem coraz ciężej przeglądać mi bazę w SQL Developerze. Tabelek z danymi mam w tej chwili niespełna 20, a tabelek z relacjami ponad 50.

Rozwiązanie, które nie istnieje

To, czego brakuje w obecnej sytuacji, to możliwość użycia jednej tabelki pośredniej dla kilku relacji. Wydaje mi się, że jest to niemożliwe w JPA 2.1. Wyobrażam sobie, że od strony kodu wyglądać mogłoby to tak:

W ten sposób nasza implementacja JPA, np. Hibernate, wiedziałaby, że aby utworzyć relację między filmem i scenarzystą, musi dodać wpis do tabelki movies_staff, dodatkowo ustawiając wartość kolumny staff_type na "STORY_AUTHOR". Abstrahuję tutaj od potencjalnych problemów wydajnościowych. Star Trek API, z maksymalnie kilkoma tysiącami encji w każdej z tabelek, z pewnością nie cierpiałoby z powodu problemów z wydajnością. Od strony SQL-a takie rozwiązanie również nie wydaje mi się niemożliwe.

Any help?

Byłoby wdzięczny za sugestie, czy da się opisywany problem jakoś rozwiązać. Może istnieją jakieś przeciwskazania do implentacji tego rodzaju funkcjonalności? A może istnieje ona w Hibernate, ale nie w JPA?

Wczoraj zadałem podobne pytanie na StackOverflow. Okazało się, że to, o czym piszę, rzeczywiście nie jst możliwe w JPA, ale jest możliwe do osiągnięcia z użyciem DataNucleus, frameworka, który również implementuje JPA.

Czy zmienię implementację JPA dla jednej funkcjonalności? Pewnie nie, ale dobrze mieć opcje.

Projekty po godzinach

Zasadniczo robię dwa typy projektów po godzinach. Te, które mają mnie czegoś nauczyć i te, które mają przynieść jakąś wartość innym. Projektem drugiego typu jest Star Trek API, nad którym pracuję w ramach tegorocznego konkursu DSP.

Zrobiłem przez ostatnie trzy lata kilka projektów na GitHubie i dwa poza nim – nieudane POC-i. Historia większości z nich była podobna – zaczynałem je robić, po czym z różnych powodów je porzucałem.

Moje projekty

  • Mój pierwszy wrzucony na GitHuba projekt, a jednocześnie jedyny w tej chwili ukończony, to simone, javascriptowy manager okien. Zyskał nawet kilka gwiazdek na GitHubie i parę osób wykonało forki. Na szczęście ten projekt nie wymaga ode mnie utrzymania, być może dlatego, że nikt go na poważnie nie używa.
  • Kolejny był qunit-desktop-notifications, plugin do QUnita, który prawie ukończyłem, ale straciłem nim zainteresowanie, ponieważ skończyłem w pracy projekt, w którym QUnit był w użyciu. Ten projekt, w przeciwieństwie do simone, był już budowany na Travisie i miał raporty z pokrycia kodów testami trzymane na Coveralls. Jego zakres był tak wąski, że nie sądzę, żeby kiedykolwiek komuś się przydał.
  • Następnie był ComicCMS2, próba stworzenia następcy dla ComicCMS. Dodatkowo realizowanym celem było zaznajomienie się z technologiami, których miałem używać w nowej pracy. Były to głównie Zend 2, Doctrine, PHPUnit i Angular. Porzuciłem ten projekt, ponieważ zmieniałem technologię na Javę.
  • Aby zmienić technologię na Javę, rozpocząłem projekt Carmen. Miał to być projekt, który pozwoli na zbieranie statystyk na temat wszystkich repozytoriów na GitHubie, i, z czasem, także statystyk z innych źródeł (Bitbucket, GitLab, itd.). Zawiesiłem go na czas robienia Star Trek API, ale widzę jeszcze jakąś szansę, że do niego wrócę, w przeciwieństwie do wcześniejszych projektów.
  • Były też 2 projekty, które nie wyszły poza fazę proof of concept. Pierwszym z nich była pisana w Rubym aplikacja, która miała za zadanie porównywać pliki PSD. Została porzucona, ponieważ bibliteka do parsowania PSD, psd.rb, była zabugowana w stopniu, który uniemożliwiał pracę nawet z prostymi plikami.
  • Drugim projektem, który nie wyszedł poza fazę POC, była próba przepisania części Gita na PHP, w celu umożliwienia wrzucania kodu na serwery tam, gdzie nie było Gita – a pracowałem w PHP z klientami, którzy mieli naprawdę archaiczne i ubogie serwery, i żadnej woli upgrage’u. Porzuciłem projekt, ponieważ przepisywanie kodu z C i implementacja protokołu Gita w PHP mnie przerosły.
  • Był jeszcze mały projekt szkoleniowy, z którym chciałem się nauczyć Django i Reacta, ale spędziłem nad nim raptem 4 dni.

Wnioski

W eseju Katedra i bazar znajdujemy zdanie mówiące, że every good work of software starts by scratching a developer’s personal itch. Dobrze sprawdza się to w moim przypadku. Problem w tym, że wówczas, gdy personal itch przestaje mi dokuczać, także projekt przestaje być przeze mnie rozwijany. I tak stało się z kilkoma z nich. Dostrzegam tutaj wzorzec.

Wydaje mi się też, że nie idzie mi dobrze łączenie prób nauki technologii z próbami stworzenia wartości dla innych. Robiąc projekt, który ma mnie nauczyć technologii, z konieczności wybieram coś łatwiejszego w stosunku do tego, na co mógłbym się zdecydować, gdybym pracował ze znaną mi technologią. Tym samym, gdy zaznajomię się już z technologią, projekt przestaje być interesujący.

Kilka moich projektów wzięło się z problemów, które napotykałem w pracy. Przeglądarkę diffów plików PSD chciałem zrobić dlatego, że graficy, z którymi pracowałem, nie informowali o zmianach, które robili, ograniczając się do przesłania nowej wersji pliku. Reszty trzeba było się domyślać. qunit-desktop-notifications chciałem zrobić dlatego, że javascriptowe testy długo trwały i nie wiedziałem, kiedy się skończyły, więc chciałem mieć tę informację w notyfikacji dekstopowej. Implementację części Gita w PHP chciałem zrobić dlatego, że klienci, z którymi pracowałem, nie mieli Gita na swoich serwerach. Z czasem, gdy to przestawało być moim problemem, przestawałem się interesować projektami, które miały rozwiązać problem.

Wydaje mi się, że lepiej wychodzi mi robienie projektów, gdzie znajduję coś, czego jeszcze nie ma, co mnie fascynuje, i co jest w moim zasięgu. Wówczas jest szansa, że to skończę. A co do tych projektów, w których tylko uczę się technologii, być może po prostu trzeba zaakceptować, że będą one porzucone, gdy już z zaznajomię się z technologią, w której naukę celowałem.