Szybka aplikacja w Angularze: Backend

in #polish7 years ago (edited)

Proces nauki nowej technologii zawsze rozpoczynam od przejścia możliwie najkrótszego kursu wprowadzającego, później piszę prostą aplikację wykorzystującą nowo zdobyte umiejętności by na końcu znając już swoje braki powrócić do zgłębiania aspektu teoretycznego, polega to zazwyczaj na głębszym studiowaniu dokumentacji. Dzięki tej metodzie udaje mi się zachować przyzwoite proporcje wiedzy teoretycznej oraz praktycznych umiejętności.

Link do źródła projektu.

Najpierw była idea

Po przejściu kursu Angular 4 tutorial for beginners zacząłem się zastanawiać co by tu ciekawego sklecić. Wpadłem na pomysł by napisać MVP, który wyświetlałby liczbę postów z określonego przedziału czasu z zdefiniowanych tagów znajdujących się portalu Wykop.pl. Portal ten wyświetla jedynie statystyki z całego dnia a ja chciałem pokazać co się dzieje w ostatnich 10 sekundach, 30, 2 minutach, 5 minutach i tak dalej. Chodziło mi o ukazanie szaleństwa postowania w tagach na mikroblogu w trakcie eurowizji, meczu popularnych zespołów piłki nożnej, strimów danielamagicala czy innych emocjonujących dla społeczności wydarzeniach. Aplikacja miała posiadać prosty panel admina w którym można dodawać oraz usuwać tagi oraz stronę główną wyświetlającą wyniki z możliwością ukrywania wybranych pozycji. Oczywiście z punktu widzenia biznesowego czy nawet użytkowego pomysł ten nie miał zbyt dużej racji bytu jednak nie o to mi chodziło.

A później trzeba było wybrać technologię

Wiedząc czego mniej więcej chce przyszła pora na wybór odpowiednich technologii. Początkowo myślałem o tym by skorzystać z jakiego frameworka javowego, jednak stwierdziłem, że język ten oraz cały jego ekosystem jest trochę toporny a zależy mi na tym by jak najszybciej napisać tą część aplikacji i skupić się na Angularze, dlatego zdecydowałem się na pythona, którego znam na przyzwoitym poziomie. Jako framework webowy wybrałem Flaska, który jest prosty, minimalistyczny i łatwo się w nim strzela szybkie rzeczy. Wiedziałem, że nie potrzebuje relacji pomiędzy modelami więc zdecydowałem się skorzystać z bazy NoSQL Redis. Do łączenia się z wykopowym API wybrałem bibliotekę wykop-sdk. Frontend to oczywiście Angular 4 oraz MaterializeCSS by stworzyć ładny graficzny interfejs w Material Design. Całość wygląda następująco:

Do roboty

Wszystkie widoki postanowiłem umieścić w app.py, jest to główny plik w którym tworzony jest obiekt aplikacji Flaska. Stworzyłem moduł o nazwie component w którym utworzyłem trzy pliki - microblog.py zawierający logikę obsługującą i przetwarzającą zapytania api do wykopu, redis_workspace.py do posługiwania się redisem oraz plik worker.py zawierający Workera czyli proces chodzący w tle który pobiera dane o postach z tagu i zapisuje je w bazie.

Baza i liczniki

Całą koncepcję oparłem na wygasających licznikach. W bazie zapisywałem klucze w postaci {tag}:{okres_czasu} i ustawiałem auto wygaszanie na wartość {okres_czasu}. Gdy pojawił się nowy post zwiększałem wartość liczników o 1, gdy licznik się wygaszał, w czasie inkrementacji tworzony był nowy z wartością 1. To był generalnie zły pomysł jak wyszło w praktyce. Po pierwsze wykop nie pozwala na zbyt częste zapytania co skutkowało tymczasową blokadą, więc musiałem zapomnieć o badaniu krótkich przedziałów czasu w stylu 10, 30 sekund. Po drugie występowała z pozoru dziwna sytuacja:

Dlaczego klucz o żywotności 5 minut ma wartość większą niż klucz o żywotności 9 minut? Odpowiedź jest bardzo prosta - liczniki obejmowały różne przedziały czasowe.

Zawsze można dać potęgi jakieś dowolnej liczby na przykład 2?

Nie bardzo to działa z powodu wartości N. Jako N rozumiem sumę złożoności obliczeniowej oraz wszelkiego rodzaju opóźnienia wynikające z połączeń z bazą oraz wykopowym api. N nie jest stałe.

Po kilku bezowocnych próbach zmieniłem całkowicie podejście. W bazie pod kluczem {tag}:dates zapisuję daty nowych postów a następnie przeliczam je w locie względem obecnego czasu.

    # daty są uporządkowane w kolejności malejącej 
    # więc nie ma potrzeby bym sprawdzał wszystkie elementy listy
    # filter_by = datatime.now()
    def get_counter(dates, filter_by):
        counter = 0
        for date in dates:
            if date >= filter_by:
                counter += 1
            else:
                return counter
        else:
            return counter

Widok

Widoki są banalnie proste.

class TagRemoveView(Resource):
    @staticmethod
    def delete(tag):
        password = request.headers.get('Authorization')

        if password != ADMIN_PASS \
                or not tag \
                or micro.is_empty_tag(tag):
            return False

        redis.remove_tag_keys(tag)
        redis.remove_tag_from_list(tag)
        return True

Metoda delete reprezentuje metodę żądania http. Do wyświetlania zawartości stosuje się GET, do usuwania DELETE, do nadpisania PUT a do dodania wartości do bazy POST. request.headers.get('Authorization') pobiera wartość z nagłówka HTTP o kluczu Authorization. Generalnie wszelkiego rodzaju tokeny trzyma się w nagłówkach. Warto też dodać, że z powodu zastosowania przeze mnie Flask-RESTful nie muszę ręcznie zakodowywać danych do jsona, następuje to automatycznie. Dobrą praktyką jest również zwracanie odpowiedniego statusu HTTP. Lista jest dostępna TUTAJ. W naszym przypadku robi się to zwracając return True, 200 gdzie 200 to wartość statusu. Ja sobie to podarowałem świadomie stosując pewne skróty oraz uproszczenia.

CORS

Przeglądarki nie pozwalają odwoływać się skryptom do zdalnych zasobów z przyczyn bezpieczeństwa. Można to jednak wyłączyć ustawiając CORSy. Generalnie zamiast gwiazdki, która oznacza, że zapytania są akceptowane od wszystkich stron lepiej określić konkretną domenę/domeny.

@application.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
    response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
    return response

Podsumowanie

Jakie wnioski wyciągnąłem pracując nad częścią backendową? Najważniejsze jest skrupulatne przemyślenie danego problemu zanim przystąpi się do implementacji rozwiązania. Ważne też by opracować dobrą strukturę umożliwiającą łatwą wymianę pewnych komponentów/metod bez potrzeby gruntownej przebudowy aplikacji (to mi na szczęście wyszło).

ps. TUTAJ masz link do działającej wersji


Jeśli ci się podobało i chcesz więcej to upvote i dodanie mnie do obserwowanych zawsze będzie dodatkową zachętą do pracy 😉

Sort:  

tak z innej beczki, ten szablonik keikaku.eu sam robiłeś? bardzo ładny.

bardzo mi się podoba, czy mógłbyś mi podesłać pliki szablonu? chciałbym zrobić stronę z moimi filmikami z yt, dtube i wpisami na steemit. to jest właśnie to czego szukam

bardzo dziękuję, podesłałbyś mi ten układ z dwoma poziomymi prostokątami, te proporcje są idealne. z 2 lata nic nie kodowałem, poradzę sobie, ale gotowiec mi mi ułatwił życie

https://github.com/lukas346/microblog/tree/master/microblog/templates

najważniejsze to base.html oraz articles/article_list.html

okej, przysiądę w niedzielę, powinienem dac radę. Mam jeszcze jedno pytanko. Chciałbym żeby nie było paginacji, tylko strona doczytywała wraz ze schodzeniem niżej. Znasz jakieś skrypty?