Archiwum autora: Techniczny Inside

Mutagen – synchronizacja plików z Dockerem i nie tylko

W codziennej pracy, programiści pracując na dużej ilości plików chcą mieć do nich bezpośredni i szybki dostęp, a efekt zmian w kodzie powinien być widoczny bez zbędnych i dodatkowych działań. Po drodze spotykamy wiele blokad związanych z konfiguracją, zastosowaną technologią, używanym systemem operacyjnym lub źródłem danych.

Przykłady?

– pracujesz nad dużym projektem PHP w środowisku Dockera na OSX – okazuje się, że montowanie twoich lokalnych plików drastycznie spowalnia działanie aplikacji, a uruchomienie composer install trwa wieczność.

– jesteś zmuszony do wykonania bezpośredniej akcji na plikach znajdujących się na serwerze. Praca przez SSH okazuje się uciążliwa i marzysz o tym, żeby móc pracować na tych plikach lokalnie – w swoim IDE.

– rozpoczynasz pracę nad zdockeryzowaną aplikacją (np. fluentbit, elasticsearch, solr), której konfiguracja zaszyta jest w obrazie. Zamiast tworzyć pliki konfiguracyjne lokalnie i odpowiednio je montować, wolałbyś mieć do nich bezpośredni dostęp „na żywo”.

– pracujesz lokalnie na Ubuntu, a twoja aplikacja uruchomiona jest w Dockerze, z podmontowanymi katalogami. Okazuje się, że aplikacja ma problemy z uprawnieniami do plików i katalogów lub je zmienia, a to tylko początek problemów.

Mutagen

Takie problemy (uproszczone na potrzeby artykułu) można mnożyć bez końca, ale jak sobie z nimi radzić? Z pomocą przychodzi nam jedno narzędzie – Mutagen (https://mutagen.io/).

Mutagen jest narzędziem open-source umożliwiającym proste, wielostronne, w pełni konfigurowalne i błyskawiczne synchronizowanie plików. W porównaniu z podobnymi narzędziami używanymi dotychczas (np. Unison, docker-sync) charakteryzuje się wysoką wydajnością, stabilnością, prostotą użycia, wieloplatformowością oraz aktywnym wsparciem ze strony twórców.

Ogólne działanie sprowadza się do uruchomienia sesji synchronizacji pomiędzy źródłem (alpha) i celem (beta), która na samym początku rozpoznaje użyte środowiska, aby uruchomić na nich proces „agenta„. Agenci komunikują się między sobą za pomocą dobranego protokołu (SCP, Docker API i inne) i obserwują zmiany na plikach. Dalsze działanie, przenoszenie zmian, ignorowanie konkretnych plików, zarządzanie uprawnieniami i symlinkami konfigurowalne jest za pomocą pliku yaml.

Od wersji 0.10 Mutagen wspiera również orkiestrację synchronizacji na poziomie projektu (https://mutagen.io/documentation/orchestration/) oraz kierowanie ruchem sieciowym (https://mutagen.io/documentation/forwarding/).

Poniżej przedstawiam przykładową konfigurację synchronizacji plików dla aplikacji Symfony Demo w systemie OSX z pomocą Mutagena w wersji 0.10.

Zacznijmy od instalacji Mutagena:

 brew install mutagen-io/mutagen/mutagen

Po instalacji należy uruchomić daemon process aplikacji i zarejestrować go w autostarcie systemu:

mutagen daemon start
mutagen daemon register

Mając uruchomioną aplikację Docker, z odpowiednimi kontenerami PHP i Nginx, oczekującymi na pliki w katalogu /var/www/symfony możemy uruchomić nasze sesje synchronizacji.

➜  docker-compose up -d
Creating network "sync-example_default" with the default driver
Creating sync-example_mysql_1 ... done
Creating sync-example_php_1   ... done
Creating sync-example_nginx_1 ... done


➜  docker ps
CONTAINER ID        IMAGE                COMMAND                  CREATED             STATUS              PORTS                               NAMES
f1382f624b00        sync-example_nginx   "nginx"                  15 seconds ago      Up 13 seconds       0.0.0.0:80->80/tcp                  sync-example_nginx_1
a6f42a5c3c89        sync-example_php     "php-fpm7 -F"            16 seconds ago      Up 14 seconds       0.0.0.0:9000->9001/tcp              sync-example_php_1
1a9cec92ed8e        mysql:5.7            "docker-entrypoint.s…"   17 seconds ago      Up 15 seconds       0.0.0.0:3306->3306/tcp, 33060/tcp   sync-example_mysql_1

Tworzymy plik mutagen.yml z przykładową zawartością:

sync:
  defaults:
    ignore:
      paths:
        - .DS_Store
  code:
    alpha: "./symfony"
    beta: "docker://sync-example_php_1/var/www/symfony"
    mode: "one-way-safe"
    ignore:
      vcs: true
      paths:
        - "/build/"
        - "/vendor/"
        - "/var/"
  composerjson:
    alpha: "./symfony/composer.json"
    beta: "docker://sync-example_php_1/var/www/symfony/composer.json"
    mode: "two-way-resolved"
    watch:
      pollingInterval: 2
    permissions:
      defaultFileMode: 666
      defaultDirectoryMode: 777
    ignore:
      vcs: false
    symlink:
      mode: "ignore"
  composerlock:
    alpha: "./symfony/composer.lock"
    beta: "docker://sync-example_php_1/var/www/symfony/composer.lock"
    mode: "two-way-resolved"
    watch:
      pollingInterval: 2
    permissions:
      defaultFileMode: 666
      defaultDirectoryMode: 777
    ignore:
      vcs: false
    symlink:
      mode: "ignore"
  var:
    alpha: "./symfony/var"
    beta: "docker://sync-example_php_1/var/www/symfony/var"
    mode: "two-way-resolved"
    watch:
      pollingInterval: 5
  vendor:
    alpha: "./symfony/vendor"
    beta: "docker://sync-example_php_1/var/www/symfony/vendor"
    mode: "two-way-resolved"
    watch:
      pollingInterval: 5
    ignore:
      vcs: false

Zawartość pliku należy dostosować do swoich nazw kontenerów (format to docker://NAZWA_KONTENERA/SCIEZKA). Poszczególne klucze w pliku wyjaśnione są w dokumentacji pod adresem https://mutagen.io/documentation/. Podczas tworzenia konfiguracji, należy zwrócić uwagę na klucze „mode„, „pollingInterval” oraz na wielkość synchronizowanych katalogów. Każdy z tych elementów ma wpływ na wydajność i obciążenie generowane przez procesy „mutagen agent” w kontenerach i na hoście.

Moje doświadczenia potwierdzają, że nawet bardzo duży projekt może być synchronizowany w całości z minimalny obciążeniem CPU. Warunkiem koniecznym jest dobranie odpowiedniej konfiguracji, nawet kosztem dużej ilości sesji mutagena.

Tworzymy nasz projekt Symfony Demo w katalogu symfony:

composer create-project symfony/symfony-demo symfony

Następnym krokiem będzie uruchomienie stworzonych sesji projektu:

mutagen project create mutagen.yml

W rezultacie otrzymamy listę ID utworzonych sesji. Aby sprawdzić ich status należy wywołać komendę

mutagen list

Aby obserwować zachodzące zmiany oraz status transferowanych plików, możemy użyć komendy:

mutagen monitor {ID_SESJI}

Już po paru sekundach na naszych kontenerach znajduje się cała zawartość projektu. Pliki vendora będą synchronizowane dwustronnie, a pliki projektu jednostronnie (w przypadku ewentualnych konfliktów zostaną one wyświetlone po użyciu mutagen list).

Powyższa konfiguracja jest jedną z bardziej skomplikowanych. Jeżeli interesuje nas synchronizacja tylko jednego katalogu (np. konfiguracji PHP w obrazie) wystarczy, że wywołamy komendę:

mutagen create -m two-way-safe docker://sync-example_php_1/etc/php7 ~/Desktop/php

a na naszym pulpicie pojawi się synchronizowany katalog z kontenera, w praktyce działający tak szybko, jak gdyby był podmontowany.

Sesje możemy oczywiście w dowolnej chwili zatrzymywać i wznawiać, usuwać i tworzyć. Nad wszystkim panuje aplikacja.

Podsumowując, uważam, że dzięki Mutagenowi synchronizacja plików nie jest już smutną koniecznością, a narzędziem, które stwarza nowe możliwości. Możemy na przykład w prosty sposób pozbyć się obciążenia komputera, wynosząc warstwy aplikacji poza lokalne środowisko, działając jedynie na IDE.

Serdecznie polecam to narzędzie oraz zachęcam do wspierania twórcy.

Podczas tworzenia artykułu, korzystałem z poniższych linków:
https://mutagen.io/
https://github.com/mutagen-io/mutagen
https://github.com/symfony/demo
https://github.com/coloso/symfony-docker

Autorem tekstu jest Janusz Maciejewski.

NgRx – stan aplikacji angularowych

NgRx

Zarządzanie stanem aplikacji frontendowej może być trudne, a stopień trudności rośnie wraz rozwojem aplikacji.

Dzisiaj po krótce chciałbym opisać wiodącą bibliotekę pozwalającą na tworzenie i zarządzanie tymże stanem. Będzie mowa o tytułowym NgRx.

Na początek postaram się powiedzieć skąd wzięła się ta biblioteka i jakie problemy rozwiązuje, następnie omówię elementy składowe NgRx, a później przejdę do zalet i wad tego rozwiązania oraz dostępnych alternatyw.

A więc czym jest NgRx?

Zgodnie ze wpisem w dokumentacji:

NgRx jest zbiorem bibliotek do budowania reaktywnych aplikacji z wykorzystaniem Angulara. NgRx udostępnia zarządzanie stanem, izolację efektów ubocznych, zarządzanie kolekcjami encji, jest powiązany z „rouetrem” Angulara, pozwala na generowanie kodu oraz na korzystanie z narzędzi programistycznych usprawniających pracę.

Nie jest to jednak nowy pomysł w świecie frameworków frontendowych. Pracownicy z firmy Google (NgRx podobie jak Angular ma swoje początki właśnie w gigancie z Mountain View, aktualnie jest rozwijany przez społeczność) nigdy nie kryli faktu, iż główną inspiracją był Redux szeroko wykorzystywany w środowisku Reacta. Redux natomiast jest implementacją Fluxa, który został zaprezentowany jako wewnętrzna architektura w innym z amerykańskich gigantów mianowicie w znanym i lubianym Facebooku.

Elementy składowe NgRx

NgRx składa się z kliku bibliotek:

* @ngrx/store
* @ngrx/effects
* @ngrx/router-store
* @ngrx/entity
* @ngrx/schematics

Podstawowe funkcjonalności zawierają się w @ngrx/store i @ngrx/effects, @ngrx/router-store pozwala na integrację z routerem angualra, @ngrx/entity pomaga w operacjach na dużych kolekcjach, zaś @ngrx/schematics zawiera zbiór schematów pozwalających na generowanie kodu za pomocą wiersza poleceń.

Akcje

Akcja to zwykła klasa implementująca interfejs Action z @ngrx/store, posiada typ oraz opcjonalny „payload” przez, który przekazujemy dane. Zazwyczaj są wykorzystywane w komponentach w celu wykonania jakieś operacji (zmiany stanu lub/i wywołania efektu).

Reducery

Reducer jest zwykłą funkcją przyjmującą 2 parametry, aktualny stan aplikacji oraz akcję. Jego zadaniem jest zwrócić nowy stan aplikacji.

Efekty

Do efektów oddelegowane są wszystkie asynchroniczne operacje, w szczególności zapytania do api. Do efektów wstrzykujemy serwisy komunikujące się z zewnętrznymi usługami, po wykonaniu operacji efekt musi zwrócić akcję, która jest przekazywana do reducera w celu zmiany stanu.

Selektory

Selektory odpowiadają za dostarczenie danych ze stanu aplikacji.

Zalety

Największą zaletą biblioteki jest bez wątpienia rozwiązanie problemu zarządzania stanem aplikacji, dzięki temu komponenty tworzące przez programistę mogą być znacznie prostsze, ponieważ znaczna część logiki jest oddelegowana do efektów, reducerów i selektorów.
Ponadto, traktując stan jako „jedyne źródło prawdy” nie musimy przejmować się synchronizacją danych pomiędzy komponentami, które mogą prezentować te same dane w różny sposób. Zastosowanie NgRx’a można z powodzeniem stosować wraz z architekturą „smart/dump componets”, która pozwala na korzystanie z strategii detekcji zmian „OnPush”. Oprócz tego dostępne jest narzędzie dla programistów pozwalające na łatwe odnajdywanie błędów.

Wady

Do wad bez wątpienia możemy zaliczyć ilość kodu, która początkowo może wydawać się przytłaczająca. Mamy do dyspozycji narzędzie do generowania wszyskich elementów składowych, lecz na początku przygody z NgRx niewiele to daje. Bez wątpienia nie jest to najłatwiejsza biblioteka do opanowania. Wymaga również znajomości innej z bibliotek, na której jest oparta, mianowicie rxjs.

Alternatywy

Do dyspozycji mamy np. NGXS, które jest nieco prostszą i okrojoną wersją NrRx’a oraz bibliotekę Aita, która może być wykorzystywana nie tylko w frameworku Angular, lecz również w innych wiodących frameworkach js.

Autorem tekstu jest Łukasz Zięba.

GraphQL – Pobieraj tylko to, co chcesz!

Czy zdarzyło Wam się kiedyś, że zwracaliście w API dane, które nie zawsze były wykorzystywane, a jedynie przydawały się w konkretnych sytuacjach? Na przykład: potrzebowaliście listy przedmiotów tylko po nazwie, a API zwraca dodatkowo kategorię oraz powiązane przedmioty, które nie są potrzebne w danej sytuacji, jednak inne miejsca w systemie z nich korzystają (over-fetching)? Albo wręcz odwrotnie – mieliście zbyt mało danych? Przez co, celem wyświetlenia listy produktów, musieliście wykonywać kolejne requesty, by pobrać informacje o powiązanych produktach (under-fetching)?

Możemy w takiej sytuacji tworzyć dedykowane endpointy lub też parametryzować (w zależności od interesujących nas informacji), ale zarządzanie takim API może stawać się z czasem coraz bardziej uciążliwe i nieefektywne.

GraphQL

Na szczęście z pomocą przychodzi nam opracowany przez firmę Facebook silnie typowany język zapytań GraphQL. Intuicyjna składnia GraphQL pozwala na pobieranie danych (Query), ich edycję i wstawianie (Mutation) oraz obserwowanie zmian w czasie rzeczywistym (Subscription). Język ten pozwala dokładnie określić jakie dane nas interesują na wyjściu, jak głęboko chcemy pobierać obiekty powiązane ze sobą oraz co dokładnie chcemy modyfikować.

Przykłady

Tworzymy typ “Product”, który posiada nazwę, powiązane produkty oraz kategorię. Jest on wykorzystany w domyślnym typie “Query” – wymaganym do działania. Tak stworzony schemat pozwala nam na pobranie wszystkich produktów i produktów powiązanych, wraz z kategoriami.

type Query {
  allProducts: [Product]!
}

type Product {
  id: ID
  name: String
  related: [Product]!
  categories: [Category]!
}

type Category {
  id: ID
  name: String
}

Spróbujmy pobrać listę produktów (id, name), kategorie (name), oraz powiązane przedmioty (id, name) wraz z przypisanymi im kategoriami (name).

{
  allProducts {
    id
    name
    categories {
	name
    }
    related {
      id
      name
      categories {
	  name
      }
    }
  }
}

Jeśli potrzebowalibyśmy dodatkowych zagnieżdżeń, możemy je po prostu dopisać:

[...]
    related {
      id
      name
      categories {
	  name
      }
      related {
        id
        name
        categories {
	    name
        }
      }
    }
[...]

Wysyłając nasze pierwsze query do serwera, możemy otrzymać przykładowe dane:

{
  "allProducts": [
    {
      "id": 153,
      "name": "kolczyki",
      "categories": [
        {
          "name": "Biżuteria"
        }
      ],
      "related": [
        {
          "id": 156,
          "name": "pierścionek złoty",
          "categories": [
            {
              "name": "Biżuteria"
            },
            {
              "name": "Złoto"
            }
          ]
        }
      ]
    },

  [...]
  ]
}

Jak widać, otrzymaliśmy dokładnie to, o co prosiliśmy.

Przedstawiony został tu uproszczony przykład tylko dla operacji Query, więcej przykładów można znaleźć na oficjalnej stronie https://graphql.org/learn/.

Autorem tekstu jest Maksym Kuras.

API Platform

Kilka słów o API Platform

Obecnie wymiana danych między zewnętrznymi systemami jest niezbędnym elementem wszelkich aplikacji. Świetnie sprawdzi się w platformach nastawionych na sprzedaż wielokanałową, gdzie dane pochodzą z różnych źródeł. Mowa o omnichannel ecommerce. Udostępnianie danych oraz możliwość ich modyfikacji jest możliwa między innymi dzięki API, czyli ​pewnemu ściśle określonemu zestawowi reguł i ich opisów, w jaki programy komputerowe komunikują się między sobą​. Jest to chyba najbardziej znany, bezpieczny oraz optymalny sposób. Jest to przeważnie API oparte o styl architektoniczny REST.

Takie API musi być przede wszystkim zrozumiałe i czytelne dla wszystkich, którzy będą z niego korzystali. Interfejs API powinien mieć dobrą dokumentację, najlepiej z przykładami. W celu stworzenia API wraz z wartościową dokumentacją można skorzystać np. z API Platform.

API Platform

API Platform zostało utworzone w roku 2015 przez Kevina Dunglasa. Zawiera bibliotekę PHP. Jest to narzędzie oparte na frameworku PHP Symfony.

Jak szybko stworzyć API?

Poniżej przykład jak stworzyć API z użyciem API Platform dla prostego modelu CRUD.

1. Projekt tworzymy poprzez polecenie

composer​​ create-project symfony/skeleton nazwa-projektu

Utworzy nam to folder z nazwą projektu oraz zainstaluje pierwsze niezbędne paczki. symfony/skeleton t​o minimalna opcja projektu, która instaluje tylko zależności niezbędne do działania Symfony.

2. Następnie odpalamy polecenie:

composer​​ require api

które instaluje nam właśnie API Platform.

3. Teraz dla przykładu możemy sobie w katalogu Entity utworzyć prosty model, np. User.php.

<?php namespace App\Entity; use ApiPlatform\Core\Annotation\ApiResource; /** * @ApiResource */ class User { /** @var string */ private $id; /** @var string */ private $firstName; /** @var string */ private $lastName; /** @var string */ private $email; public function __construct( string $id, string $firstName, string $lastName, string $email ) { $this->id = $id;
        $this->firstName = $firstName;
        $this->lastName = $lastName;
        $this->email = $email;
    }

    public function getId(): string
    {
        return $this->id;
    }

    public function getFirstName(): string
    {
        return $this->firstName;
    }

    public function getLastName(): string
    {
        return $this->lastName;
    }

    public function getEmail(): string
    {
        return $this->email;
    }
}

4. Pod adresem http://127.0.0.1:8000/api mamy już gotową dokumentację API wraz z możliwością wysyłania requestów.

Zalety

– szybkość
– nie powtarzanie tego samego kodu w wielu miejscach
– jasna i przejrzysta dokumentacja z możliwością requestów

Jest to tylko bardzo krótki opis, gdyż API Platform posiada o wiele więcej możliwości. Są to między innymi:

– tworzenie, pobieranie, aktualizowanie i usuwanie zasobów (CRUD)
– walidacja danych
– stronicowanie
– filtrowanie
– hypermedia (JSON-LD, HAL)
– GraphQL
– Nagłówki CORS
– wsparcie dla JWT

Źródła: ​https://api-platform.com/docs

Autorem tekstu jest Piotr Smaruj.

Free Plan

O czym jest ten artykuł?

Serverless zdobywa świat! Czy Serverless to dobra nazwa? Cóż, tak to ktoś wymyślił, tak się przyjęło, nikt tego już nie zmieni. Oczywiście, małą niekonsekwencją może wydawać się obecność serwerów pod maską technologii Serverless, ale, w sumie, można powiedzieć, wszystko jest kwestią perspektywy. Co jednak ważne – technologia ta daje nam, programistom, dość ciekawe możliwości, z których, między innymi, postaram się skorzystać w tym artykule.

Od razu przyznam się też do inspiracji, w tym przypadku był nią jeden z odcinków vloga „fun fun function” – kilka pomysłów z tego odcinka wykorzystałem w moich własnych eksperymentach.

Cel – najlepiej opisać go chyba jako „wycieczkę” po narzędziach i technologiach, które, albo są już dzisiaj standardem, albo po prostu, mnie osobiście, wydają się interesujące. Wszystkie narzędzia/serwisy będą wykorzystywane tylko w wersji darmowej – stąd tytułowy „free plan” – czyli bez kosztów dla nas – deweloperów, co oczywiście jest super w zastosowaniach hobbystycznych i jednocześnie (przeważnie) zupełnie nie nadaje się do zastosowań biznesowych (z czego wszyscy, mam nadzieję, zdajemy sobie świetnie sprawę).

Jako tło dla niniejszego artykułu wykorzystałem prostą aplikację Gls (mojego autorstwa), której główną funkcjonalnością jest animowanie obiektów Gls (wiem, na razie to pewnie niewiele mówi). Aplikacja posłuży mi tutaj głównie jako przykład praktycznego wykorzystania poszczególnych narzędzi.

Zapraszam do lektury!

Narzędzia

Programiści, jak wszyscy rzemieślnicy (Software craftsman) potrzebują dobrych narzędzi!

Pisząc ten artykuł wykorzystałem: GitHub, Docker Hub, Node.js, Npm, Webpack, Unpkg, OpenShift, Observable, Firebase, Heroku i Miro. Dodatkowo chciałem, ale musiałem zrezygnować ze względu na uciekający czas, skorzystać również z CircleCI, Travisa, Zeit i wypróbować CloudFlare – może następnym razem się uda.

Niestety, nie starczy tutaj miejsca na dokładniejszy opis wszystkich tych technologii, w paragrafie „Repetytorium” (nazwa trochę przewrotna) umieszczam krótką informację odnośnie tych z powyższych narzędzi, z których dokładniejszego opisu tutaj zrezygnowałem. Oczywiście, dla lektury samego artykułu, znajomość wszystkich tych technologii nie jest istotna – cel to w końcu, również, ich prezentacja (choć niekiedy bardzo krótka) czytelnikowi.

Diagram – spojrzenie na aplikację

Nic tak nie ułatwia zapoznania się z jakimś większym planem, jak spojrzenie na diagram przedstawiający jego najistotniejsze elementy. Miro jest jednym z narzędzi, które pozwalają tworzyć diagramy, czy „wirtualne tablice”, do wykorzystania w pracy zespołowej. Aby przetestować ten serwis (choć w zespole jednoosobowym) postanowiłem stworzyć, za jego pomocą, trochę schematów odnośnie aplikacji Gls (o której więcej za chwilę). Schematy te są dostępne pod adresem https://miro.com/app/board/o9J_kxlNBbU=/ (oczywiście stworzone są w ramach konta „free”). Umieściłem tam również Kanban Board realizacji Gls – jak widać, niestety nie wszystko udało się zakończyć.

Jeżeli ktoś zna/pracował z Enterprise Architect to muszę zaznaczyć, iż Miro, moim zdaniem, nie stanowi jego odpowiednika, a raczej uzupełnienie (ciekawą możliwością byłaby migracja schematów pomiędzy tymi dwoma narzędziami). To, czego brakuje mi w Miro na dzień dzisiejszy, to z pewnością możliwość tworzenia diagramów UML – Miro to bardziej narzędzie ogólnego przeznaczenia, z naciskiem na biznesowe prezentacje, niż narzędzie dedykowane programistom. Ale chyba warto obserwować ten projekt.

Aplikacja

Aplikacja Gls składa się z poniższych elementów – wszystkie zostaną omówione/wykorzystane:

• Silnik “przeliczający” obiekty Gls – biblioteka Gls
• Prezentacja obiektów Gls – aplikacja Gls-Web
• Wsparcie przy edycji obiektów Gls – Observable, tylko edycja, edycja + zapis do bazy
• Client API dostępu do bazy danych – Gls-Db-Api-Client
• API bazy danych – Gls-Db-Api
• Baza danych

Wszystkie te elementy zostały zaznaczone na poniższym schemacie:

Pod tym linkiem znajduje się  „interaktywna” wersja powyższego diagramu z linkami prowadzącymi do odpowiednich „elementów” aplikacji,
link ukryty pod nazwą “Gls-Web Hosting” powinien prowadzić do wersji aplikacji hostowanej na platformie OpenShift. Tutaj małe wyznanie – korzystanie z “free plan” ma oczywiście wady o których należy pamiętać – jedną z nich jest z pewnością duża „dynamika” dostępności elementów aplikacji. Oznacza to ni mniej, ni więcej, tylko tyle, że adresy url do elementów hostowanych na platformach Serverless (przynajmniej w przypadku aplikacji Gls) mają dużą tendencję do zmian – dlatego unikam w tym artykule wklejania bezpośrednich linków do aplikacji/“endpointów”, ponieważ artykuł raczej nie będzie edytowany – zamiast tego będę się starał utrzymywać w miarę aktualne linki na schemacie dostępnym poprzez Miro.

Kto odważny – niech kliknie w link z chmurki „Gls-Web Hosting”!

Funkcjonalność aplikacji nie jest tutaj najważniejsza (to nie jest artykuł o aplikacji Gls!), nie jest też celem udowodnienie słuszności zastosowanej architektury – najpewniej inni programiści rozwiązali by pewne kwestie inaczej – to tylko przykład!

Uwaga co do nazewnictwa: nazwy Gls używam w kilku kontekstach:

• Gls jako cała aplikacja – cały projekt
• Gls jako obiekt json – czyli dane do wyświetlenia / renderowania
• Gls jako biblioteka https://github.com/lbacik/gls

Zachowajcie czujność!

Repetytorium

Tak jak zaznaczałem w paragrafie „Narzędzia”, poniżej zamieszczam „kilka słów” o niektórych z wykorzystanych technologii:

Git & GitHub

Stworzony w pierwszej dekadzie tego wieku przez Linusa Torvaldsa Git, to jeden z aktualnie najpopularniejszych rozproszonych systemów kontroli wersji. GitHub natomiast to… chyba można ten serwis nazwać jednym ze współczesnych „bazarów” (nawiązując do eseju Erica Raymonda „Katedra i Bazar” – ciekawym może wydawać się fakt, iż obecnie właścicielem serwisu GitHub jest firma Microsoft). W tym miejscu, zamiast linków do dokumentacji (myślę, że w razie potrzeby znalezienie w Internecie informacji o tych narzędziach nie będzie żadnym problemem), chciałbym polecić (tym którzy jeszcze nie oglądali) prezentację Linusa, dotyczącą Git-a, wygłoszoną w siedzibie Google 12 lat temu: https://www.youtube.com/watch?v=4XpnKHJAok8.

Kontenery, Docker & Docker Hub

Docker nie jest jedynym, aktualnie dostępnym, wysokopoziomowym narzędziem do zarządzania Linuksowymi Przestrzeniami Nazw (Linux Namespaces) – ale chyba przyjął się najlepiej! W końcu ilu deweloperów zna Lxc/Lxd czy Rkt? Ponieważ wszystkie te narzędzia są mimo wszystko dość podobne (wszystkie wykorzystują ten sam mechanizm, którym są Linuksowe Przestrzenie Nazw (Linux Namespaces), to trochę może dziwić tak duża dysproporcja w ich rozpoznawalności wśród programistów (i nie tylko). Co więc stoi za tak dużym sukcesem Dockera? Każdy może mieć oczywiście swoją teorię, ja stawiałbym na serwis Docker Hub – moim zdaniem, globalne repozytorium „dockerowych” obrazów (docker images), to był przysłowiowy strzał w dziesiątkę!

Do aplikacji Gls-Web dodany został plik Dockerfile. To pozwala zbudować każdemu kontener lokalnie. Taki „obraz”, w przypadku własnych projektów, można wysłać do repozytorium Docker Hub, aby udostępnić go innym użytkownikom. Jednak, jeżeli eksport nie jest jednorazowy i chcemy utrzymywać synchronizację pomiędzy projektem w serwisie GitHub a obrazem w serwisie Docker Hub to lepszym rozwiązaniem będą aktualizacje automatyczne. W tym celu, dla projektu na Docker Hub, możemy wybrać integrację z GitHub (do wyboru jeszcze Bitbucket), a następnie ustawić „wyzwalacz” (trigger) na wskazany branch w repozytorium Git. Oznacza to, że po wprowadzeniu zmian do tego, wybranego brancha, Docker Hub automatycznie rozpocznie budowanie kontenera wg pliku Dockerfile w repozytorium. Tak skonfigurowałem to właśnie w przypadku Gls-Web i Gls-Db-Api – obrazy Gls-Web i Gls-Db-Api są budowane automatycznie po każdej zmianie w projektach (branch master) Gls-Web i Gls-Db-Api!

Więcej informacji w dokumentacji na docker.com, oraz w pliku README.md projektu Gls-Web.

Node.js, Npm, Webpack & Unpkg

Aplikacja Gls została napisana w JavaScript, z wykorzystaniem Node.js.

Node.js to „środowisko uruchomieniowe” dla języka JavaScript, które powstało z myślą o wykorzystaniu JS w tzw. backendzie. Termin „środowisko” okazuje się mieć tutaj znaczenie, co w świetny sposób tłumaczy Philip Roberts w prezentacji: What the heck is the event loop anyway?, opisującej sposób działania JS w przeglądarce jak i w implementacji Node.js.

Npm to manager pakietów dla Node.js – jednak, nie tylko. Pozwala on również na publikowanie własnych bibliotek w centralnym repozytorium (tak, aby inni programiści mogli je pobrać i wykorzystać w swoich projektach).

Aby umożliwić korzystanie z biblioteki Gls (i Gls-Db-Api-Client) w środowisku JS przeglądarki skorzystałem natomiast z Webpack i Unpkg – poniżej wyjaśnię ich rolę, to w sumie ciekawa kwestia świata JS.

Gls-Web wykorzystuje bibliotekę Gls w dość specyficzny sposób. Gdy kod biblioteki Gls na zostać dołączony do dokumentu HTML wysyłanego do przeglądarki, “backend” korzysta z metody code, klasy Generator, której rezultatem jest zwrócenie (w postaci ciągu znaków) całego kodu biblioteki Gls potrzebnego do renderowania obiektów Gls w przeglądarce (kod ten możemy znaleźć w źródle dokumentu HTML).

1. To dość toporna metoda.
2. Z metody takiej nie możemy skorzystać w aplikacjach typu Observable (o Observable napiszę w jednym z kolejnych paragrafów) ponieważ nie mamy dostępu do kodu działającego w backendzie.

Punkt pierwszy możemy rozwiązać/poprawić przygotowując wersję naszej biblioteki w formacie UMD. Aby rozwiązać problem z pkt 2 możemy udostępnić przygotowaną wersję UMD poprzez serwis typu Unpkg.

Weźmy bibliotekę Gls – jeżeli chcielibyśmy „wczytać” ją z poziomu przeglądarki, wykorzystując np. link https://unpkg.com/@lbacik/gls (Unpkg zaserwuje nam w takim przypadku plik wskazany w package.json jako „main” – czyli naszą fabrykę to tworzenia obiektów Gls), to niestety to nie zadziała – runtime JS uruchomiony w przeglądarce nie będzie wiedział jak interpretować polecenia „require” – czyli jak dołączyć pozostałe pliki pakietu…

Tutaj na scenę wchodzi UMD – specyfikacja ta pozwoli nam przygotować odpowidnią wersję biblioteki Gls. Oczywiście nie chodzi o przepisanie biblioteki od zera, a wykorzystanie jednego z narzędzi, które skonwertuje naszą bibliotekę (przygotowaną dla Node.js) do odpowiedniego formatu (umożliwiającego jej wykorzystanie w środowisku przeglądarki) – ja zdecydowałem się na wykorzystanie do tego celu właśnie narzędzia Webpack. W katalogu głównym biblioteki Gls umieściłem plik konfiguracyjny webpack.config.js, oraz przygotowałem „skrót” do wywołania webpacka w `package.json`, sama biblioteka natomiast wskazana jest w zależnościach środowiska developerskiego aplikacji.

Wywołując przygotowany skrót:

$ npm run build

generowana jest wersja pakietu w formacie UMD. Ta wersja zostaje zapisana w katalogu `dist`. Sam katalog `dist` wyłączony jest z zapisu w repozytorium git (plik .gitignore), ale włączony jest do uploadu do repozytorium npmjs.com (plik package.json, klucz „files”). Czyli, po opublikowaniu pakietu na npmjs.com, wersja pakietu Gls przygotowana przez narzędzie Webpack będzie dostępna, poprzez serwis Unpkg, pod adresem url: https://unpkg.com/@lbacik/gls/dist/gls.js

I to jest to! Skorzystamy z tej wersji pakietu Gls (jak i również analogicznie przygotowanej wersji pakietu Gls-Db-Api-Client) w aplikacji Observable. Oczywiście w profesjonalnych aplikacjach takie „dystrybucyjne” wersje powinny być jeszcze minimalizowane, aby ograniczyć ich rozmiar, ale ja już odpuściłem ten krok (przynajmniej w aktualnej wersji aplikacji Gls).

Serverless – starcie pierwsze

Czas na pierwsze starcie z Serverless! Docker Hub to bardzo przydatne narzędzie – jednak, dany kontener, musimy pobrać i uruchomić lokalnie (bądź też, mówiąc bardziej ogólnie – w ramach swojej infrastruktury). To nie sprawi problemu osobom zaznajomionym z tą technologią, jednak większość potencjalnych użytkowników wolałaby chyba po prostu dostać adres url, który mogliby „kliknąć” i cieszyć się aplikacją. Czy udostępnienie takiego, globalnego adresu url, bez opłacenia dostępu do jakiejś platformy hostingowej, serwera VPS czy tego typu rzeczy, jest możliwe? Okazuje się, że aktualnie, w tej kwestii „nie jest źle”, a z pewnością, w przyszłości, będzie coraz lepiej!

W pierwszym starciu sięgnąłem po sprawdzone (w moim przypadku) już rozwiązanie – openshift.com.

OpenShift to platforma rozwijana przez Red Hat i zbudowana z wykorzystaniem technologi od Google (przynajmniej pierwotnie) – Kubernetes. W planie „free”, mamy do dyspozycji 4 pody (każdy z 0.5 GB Ram) – to naprawdę nieźle! W trakcie konfiguracji projektu możemy wskazać dockerowy image dostępny np. w serwisie Docker Hub, image ten zostanie pobrany i uruchomiony w jednym z podów, domyślnie, bez możliwości dostania się do aplikacji z Internetu, ale jednym/dwoma kliknięciami, bez problemu, zdefiniujemy odpowiedni routing. Po aktualny adres aplikacji Gls-Web hostowanej na OpenShift odsyłam do schematu na Miro – chodzi mi tutaj o link opisany na diagramie jako „Gls-Web Hosting” – adres, który ukrywa się pod tym linkiem, będzie raczej trudny do zapamiętania, ale na testy/demo w zupełności wystarczy.

Tak więc, użytkownik nie musi pobierać kodów źródłowych z GitHub (i budować aplikacji lokalnie), czy kontenera z Docker Hub – możemy przygotować również opcję z dostępem po url!

Observable

Gls-Web pozwala przeglądać przygotowane wcześniej obiekty Gls. Brakuje jednak jakiegoś „edytora” tych obiektów – narzędzia, które można by wykorzystać przy ich tworzeniu.

Obiekty Gls to obiekty typu json, kilka przykładów można znaleźć w katalogu examples projektu Gls-Web (przykłady z tego katalogu są wyświetlane w podstawowym trybie pracy aplikacji Gls-Web, są to przykłady z zakładki „local” – bądź też jedyne widoczne przykłady, jeżeli url do bazy danych nie został przekazany do Gls-Web).

Oczywiście, można rozbudować aplikację Gls-Web o jakiś prosty edytor plików Gls, ale… zróbmy użytek z naszej dekompozycji i spróbujmy poeksperymentować z Observable!

Ogólnie Observable można chyba nazwać narzędziem do zabaw z JavaScriptem. Dostarcza ono nam dość szczególnego środowiska pracy dla naszych skryptów – pozwalającego na przygotowanie interakcji w (jak dla mnie) dość nowatorki sposób. Dysponujemy notatnikiem w którym możemy osadzać skrypty i elementy HTML – skrypty oczywiście mogą wymieniać między sobą dane, kontrolować różne osadzone elementy, takie jak suwaki, czy renderować grafikę. Wszystkie zmiany w notatniku są śledzone, mamy możliwość „forkowania” notatników innych użytkowników i modyfikowania ich na własny użytek… ciekawe?

Pierwszy notatnik który przygotowałem: https://observablehq.com/@lbacik/untitled (za nic nie udało mi się przemianować tego „untitled” na „gls” – bug?)

Nie jest on zbyt skomplikowany – zawiera trzy elementy:

1. obiekt GLS (a raczej obiekt Canvas na którym renderowany jest obiekt Gls)
2. import fabryki GLSFactory z biblioteki Gls – w formie modułu UMD i z wykorzystaniem Unpkg
3. animation loop – potrzebne aby wszystko ładnie działało

Elementy notatnika możemy podglądać i edytować po wybraniu opcji „edit” z menu kontekstowego elementu, dostępnego po najechaniu na „trzy kropki”, które pojawiają się gdy kursor przesuniemy blisko lewej strony elementu).

Podglądając np. drugi element:

GlsFactory = class

Powinniśmy zobaczyć następujący kod:

GlsFactory = require('https://unpkg.com/@lbacik/gls/dist/gls.js')

Wygląda znajomo?

Observable automatycznie wykonuje kod, który umieszczamy w notatniku, tak więc nasz notatnik staje się… aplikacją. Jeżeli teraz otworzymy pierwszy z elementów i zaczniemy modyfikować kod naszego obiektu Gls (json) to efekt zobaczymy od razu po „przesłaniu/wgraniu” zmian (symbol “strzałki”, jak na klawiszu play w odtwarzaczach, widoczny w prawym-górnym rogu edytowanego elementu notatnika).

Problem, którego na razie nie udało mi się rozwiązać w zadowalający sposób to sposób prezentacji – czegoś tutaj jeszcze brakuje, czegoś dzięki czemu można by było bardziej „swobodnie” zarządzać położeniem elementów notatnika, choć, nawet bez tego, myślę, że jest świetnie!

Powrócę jeszcze do tego serwisu w jednym z kolejnych paragrafów.

Baza Danych

Baza danych przyda się, abyśmy nie musieli publikować nowej wersji Gls-Web za każdym razem, gdy chcemy pochwalić się nowym obiektem Gls (które to obiekty aktualnie są „przechowywane” jedynie jako pliki json w katalogu examples projektu Gls-Web). Dzięki wykorzystaniu bazy zyskamy centralne repozytorium dla naszych plików json!

Po bazę, (śladami MPJ), udałem się do Googla – mamy tutaj interesującą opcję NoSQL – Firebase!

Aktualnie możemy korzystać z dwóch „wersji” usługi firebase

1. Realtime Database – kto nie słyszał, niech się nie martwi, bo ta wersja określona jest już jako „przestarzała”
2. Cloud Firestore – nowa odsłona

Dla mnie przy wyborze najważniejsze były limity przy planie free i tutaj (jakżeby inaczej) – Realtime Database, jako technologia „przestarzała”, daje poszaleć znacznie bardziej. Tak więc wybór padł właśnie na tę wersję!

Realtime Database to tak naprawdę… plik json! Choć w rzeczywistości to nie jest aż tak proste, bo możemy definiować zasady dostępu do poszczególnych elementów (węzłów) naszego pliku (naszych danych), metody uwierzytelnienia czy walidację – ogólnie można wykazać się inwencją, ja jednak ograniczę się do minimum (choć opcja walidacji przesyłanych danych jest kusząca i może do niej wrócę w przyszłości).

Polecam założyć konto i wypróbować usługę – interfejs jest bardzo przystępny i obsługa bazy nie powinna sprawiać dużych kłopotów.

Ponieważ dostęp bezpośredni może być trochę kłopotliwy dla klientów (Gls-Web i Observable) i w sumie powodował by duplikację kodu, dlatego realizowany będzie przez API (Gls-Db-Api). Dodam również klienta (Gls-Db-Api-Client), aby miało to bardziej „zwięzłą” formę, i aby dany serwis mógł praktycznie od razu wywoływać metody pobierające bądź przesyłające dane do bazy.

Serverless – starcie drugie

I kolejne starcie z Serverless – tym razem sięgnijmy po Heroku (oczywiście wybieram plan free) – po sklonowaniu źródeł Gls-Db-Api, Api może zostać udostępnione za pomocą poleceń (informacje w dokumentacji: https://devcenter.heroku.com/articles/container-registry-and-runtime):

 $ heroku container:login
 $ heroku create
 $ heroku container:push web
 $ heroku container:release web

W przypadku kontenera wrzuconego przeze mnie adres został wpisany do schematu aplikacji w Miro – link do api kryje się w „chmurce” „Gls-Db-Api Hosting”.

Akcje takie jak `list` i `get` powinny być dostępne z poziomu przeglądarki (wykorzystują metodę GET protokołu HTTP):

https://API-URL/list – zwróci tablicę elementów Gls w bazie (format json)
https://API-URL/get/gls01 – zwróci konkretny obiekt json z bazy – w tym przypadku: 'gls01′

Oczywiście są różnice w stosunku do OpenShift – nie korzystamy tutaj z kontenera pobieranego ze zdalnego repozytorium (Docker Hub), tutaj budujemy taki kontener lokalnie (w swoim lokalnym systemie) a następnie wysyłamy go do serwisu heroku. Aby móc tę operację przeprowadzić musimy zainstalować klienta cli serwisu heroku. Ponadto, w odróżnieniu od OpenShift, nasza aplikacja domyślnie dostępna jest poprzez połączenie szyfrowane (https) – w przypadku OpenShift musielibyśmy sami wygenerować certyfikat i dodać go w konfiguracji routingu – nie jest to wielka filozofia, ale zawsze kilka rzeczy dodatkowych do zrobienia. Co jednak tutaj istotne, w przypadku Gls-Web, ssl nie jest dla nas specjalnie istotny, natomiast w przypadku Gls-Db-Api, w związku z danymi użytkownika wymaganymi przez akcję „add”, szyfrowanie jest już wskazane! Dodatkowo, wykorzystanie protokołu https w komunikacji ze zdalnymi serwisami jest wymagane przez serwis Observable (Observable będzie się łączyć z Gls-Db-Api wykorzystując Gls-Db-Api-Client) – połączenia korzystające z protokołu http są blokowane!

Observable DB

Wersja notatnika korzystająca z bazy to fork notatnika opisanego w poprzednim paragrafie o Observable – dodałem jedynie formatkę, służącą do wysyłania (za pomocą Gls-Db-Api-Client) edytowanego obiektu Gls do bazy. Przydałoby się tutaj jeszcze trochę pracy – nie działa to idealnie, ale dane są przesyłane i zapisywane, więc jest ok przynajmniej z technicznego punktu widzenia!

Oczywiście możliwość zapisu jest zabezpieczona hasłem, więc aby przeprowadzić własny test trzeba skonfigurować bazę i wystawić gdzieś endpoint API – ważne, aby komunikacja była po https (pal licho hasła, ale observable po prostu będzie blokował komunikację nieszyfrowaną).

Ostatnia rzecz to kwestia CORS – kod definiujący połączenie do API wygląda następująco (element poniżej formularza):

const dbapi = GlsApiClient.create('https://cors-anywhere.herokuapp.com/' + dbUrl)

Usługa https://cors-anywhere.herokuapp.com rozwiązuje problem przy małym natężeniu ruchu, więcej informacji jest w tym notatniku – również jak skonfigurować whitelistę po stronie heroku.

Podsumowanie

I w sumie to już koniec tej „wycieczki” – aplikacja Gls działa i jest dostępna (wczytywanie obiektów z bazy niekiedy trwa trochę długo, ale cóż – to są minusy, z którymi należy się liczyć w tego typu implementacjach).

Niestety nie udało mi się tutaj poruszyć wszystkich tematów, które wynotowałem sobie w pierwszym szkicu tego artykułu. Tak jak wspominałem już na początku, takie rzeczy jak CI/CD czy Load Balancing musiałem porzucić (zgodnie ze „zwinną” wersją „żelaznego trójkąta”). To oznacza, że eksperyment można kontynuować i rozszerzać jego funkcjonalność – zachęcam do tego w wolnych chwilach (w ramach swoich własnych mini-projektów, bądź w ramach Gls!). W końcu „praktyka” to podstawa, a jak się okazuje, aby podnosić nasze devopsowe umiejętności, wcale nie musimy dysponować dostępem do (jakiejkolwiek) serwerowni – wystarczy dostęp do Internetu!

Autorem tekstu jest Łukasz Bacik.

SOAP vs REST

Wymiana danych i integracja z zewnętrznymi systemami jest nieodłącznym elementem wszystkich złożonych aplikacji. Aby była możliwa, konieczne jest określenie konkretnych reguł i formatów komunikacji. Te zasady mogą określać protokoły komunikacji, czy też usystematyzowane struktury danych, artykuł skupi się na dwóch z nich: SOAP oraz REST.

SOAP (Simple Object Access Protocol) – protokół stworzony głównie na potrzeby Microsoftu w 1998 roku, w związku z zapotrzebowaniem na umożliwienie komunikacji pomiędzy aplikacjami z użyciem języka XML. Nadal szeroko używany, głównie ze względu na standaryzację, bezpieczeństwo i prostą kontrolę nad zawartością przekazanych danych.

REST (Representational State Transfer) – styl architektoniczny definiujący format przesyłanych danych, utworzony w 2000 roku przez Roya Fieldinga w ramach rozprawy doktorskiej, jako element standaryzacji protokołu HTTP. Używany ze względu na elastyczność, szybkość i prostotę. Nie jest protokołem – jako usługę RESTową można zdefiniować cache’owany, bezstanowy, komunikujący się na zasadzie klient-serwer serwis.

Format

Mnogość dostępnych formatów danych zdecydowanie przemawia na korzyść REST. Poza wiodącym JSONem, dane można wymieniać m. in. w formatach HTML, XML, YAML, czy nawet jako zwykły tekst. SOAP natomiast umożliwia komunikację jedynie z użyciem XML. Ograniczeniem RESTa jest natomiast jeden protokół – HTTP. SOAP możliwy jest dla ich szerszej gamy – m. in. HTTP, SMTP, czy UDP.

Bezpieczeństwo

SOAP wykorzystywany jest często ze względu na znaczące ułatwienia dotyczące bezpieczeństwa. Zaletami będą tutaj wsparcie dla WS-Security (rozszerzenie o elementy bezpieczeństwa, zapewniający integralność, poufność czy dołączanie tokenów do komunikatów), wbudowana logika wspomagająca komunikowanie błędów w integracji, czy łatwiejszą komunikację poprzez firewall oraz proxy. Jeśli chodzi o REST, wdrożenie analogicznych rozwiązań może być mniej wygodne. Oba rozwiązania oczywiście wspierają SSL.

Rozmiar danych

Wymuszenie przez SOAP formatu XML oraz określonych definicji w dokumencie powoduje narzut na przesyłane zapytanie. Dostępne dla REST formaty, takie jak JSON, czy YAML, mimo że mniej ustandaryzowane, jeśli chodzi o rozmiar są zdecydowanie bardziej optymalne. Różnica w rozmiarze (XML vs JSON) dla testowanych danych [6)] wyniosła około 25%.

Wydajność

REST trzeba uznać za zdecydowanie wydajniejszy. W związku z mniejszym rozmiarem wymaga mniejszej przepustowości łącza. Dla tej samej ilości danych potrzebuje również mniejszej mocy obliczeniowej. Aspekty te powodują szybsze działanie RESTa, nawet o kilkadziesiąt procent. REST również, w przeciwieństwie do SOAPa umożliwia cache’owanie wywołań API.

Zastosowanie

Funkcjonalnie patrząc, praktycznie każdą usługę sieciową można oprzeć zarówno na SOAP, jak i na REST. Z uwagi na wydajność i wygodę, obecnie zdecydowanie częściej korzysta się z RESTful API. Część istniejących już webserwisów opartych na SOAP, jest przepisywana na REST, pomimo próby zachowania obecnej funkcjonalności. Zastosowanie SOAPa jest bardziej wyspecjalizowane i może mieć on przewagę dla aplikacji, w których kluczowe jest bezpieczeństwo, jak np. różne usługi finansowe, bramki płatności.

Stanowość

Jednym z założeń RESTa jest komunikacja bezstanowa, co skutkuje brakiem tworzenia czy przechowywania sesji po stronie serwera, co wpływa korzystnie na łatwość powiększania systemu, czy jego modernizację. SOAP może działać zarówno bezstanowo, jak i z wykorzystaniem sesji.

Dane

REST traktuje dane jako zasoby; udostępnione one są za pomocą standardu URI (Uniform Resource Identifier), konkretne zasoby bywają w praktyce opisane w dokumentacji serwisu. SOAP udostępnia elementy logiki aplikacji jako usługi, definiowane według standardu WSDL opracowanego przez Microsoft i IBM. Plik definiuje, jakie informacje i w jaki sposób można wydobyć z serwisu.

Podsumowanie

Podsumowując, pomimo słabnącej popularności usług opartych na protokole SOAP i wielu zalet RESTa, nie jest możliwe jednoznaczne wybranie uniwersalnego lepszego rozwiązania. Dobór trzeba rozważyć zależnie od wdrażanej aplikacji – i mimo że w większości sytuacji najprawdopodobniej będzie to REST, SOAP nadal może okazać się przydatny.

Autorami tekstu są Łukasz Kowol i Jakub Sładek.

Źródła:
1) https://www.pearsonhighered.com/assets/samplechapter/0/6/7/2/0672326418.pdf
2) https://raygun.com/blog/soap-vs-rest-vs-json/
3) https://en.wikipedia.org/wiki/Representational_state_transfer
4) https://stackify.com/soap-vs-rest/
5) http://edu.pjwstk.edu.pl/wyklady/tbo/scb/lecture-13/lecture-13-content.html
6) https://aaltodoc.aalto.fi/bitstream/handle/123456789/29224/master_Makkonen_Joni_2017.pdf?sequence=1&isAllowed=y
7) https://www.guru99.com/comparison-between-web-services.html

Duże tablice. Jak je ugryźć?

Generator

Każdy programista w swojej pracy boryka się z problemami związanymi z użyciem pamięci. Czasami urwanie 1mb jest na wagę złota i od tego może zależeć poprawne wykonanie skryptu.

Najczęstszym problemem jest procesowanie tablic zawierających tysiące rekordów. Na szczęście z pomocą przychodzi Generator. Jest to funkcja, która pozwala na iterowanie po zbiorach danych. W PHP został dodany w wersji 5.5. Aby zacząć używać funkcji Generatora wystarczy zamiast return użyć yield.

function getData()
{
	for ($i = 0; $i < 100000; $i++)
	{
		yield $i;
	}
}

$data = getData();

Funkcja getData() zwraca obiekt klasy Generator, która implementuje interface Iterator.

Dzięki temu tablica nie jest od razu budowana w pamięci, a my mamy możliwość iterowania po rekordach.

Test

Jak wygląda kwestia wydajności? Przeprowadziłem 2 testy. Jedno przy użyciu PDO i tabeli zawierającej ok 200k rekordów oraz z tablicą również zawierającą ok 200k rekordów.

<?php
class TestGenerator
{
	private $pdo;

	public function __construct()
	{
		$this->pdo = new PDO(
			'mysql:host=127.0.0.1;dbname=test;port=3300',
			'root',
			'root'
		);
	}

	public function pdoWithGenerator()
	{
		$stmt = $this->pdo->query('select id from `test`');

		while ($row = $stmt->fetch())
		{
			yield $row;
		}
		echo 'PDO Generator: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
	}

	public function pdoWithoutGenerator()
	{
		$stmt = $this->pdo->query('select id from `test`');

		$test = [];
		while ($row = $stmt->fetch())
		{
			$test[] = $row;
		}
		echo 'PDO Brak generatora: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
		return $test;
	}

	public function testArrayWithGenerator()
	{
		for ($i = 0; $i <= 200042; $i++)
		{
			yield $i;
		}
		echo 'Tablica Generator: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
	}

	public function testArrayWithoutGenerator()
	{
		$test = [];
		for ($i = 0; $i <= 200042; $i++)
		{
			$test[] = $i;
		}
		echo 'Tablica brak generatora: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL;
		return $test;
	}
}

$test = new TestGenerator();

foreach ($test->pdoWithGenerator() as $item){}
foreach ($test->pdoWithoutGenerator() as $item){}
foreach ($test->testArrayWithGenerator() as $item){}
foreach ($test->testArrayWithoutGenerator() as $item) {}

Podsumowanie

Wnioski z tego testu są dość oczywiste. Jak widać powyżej, używając Generatora można zaoszczędzić znaczną ilość pamięci.

Ciekawostki:
* Można wykonywać operacje po yield, np. za pomocą break.

function getData()
{
	for ($i = 0; $i < 100000; $i++)
	{
		yield $i;

		if ($i === 10)
		{
			break;
		}
	}
}

$data = getData();

foreach ($data as $i)
{
	echo $i. PHP_EOL;
}

* Można zwracać pary klucz-wartość.

function getData()
{
	for ($i = 0; $i < 10; $i++)
	{
		yield $i => $i+1;
	}
}

$data = getData();

foreach ($data as $key => $value)
{
	echo 'Key: ' . $key . PHP_EOL;
	echo 'Value: ' . $value . PHP_EOL;
}

Autorem tekstu jest Łukasz Cieślik.

Co siedzi w środku tablic PHP?

Zacznę może od genezy problemu. Pracowałem nad optymalizacją zużycia pamięci w pewnym procesie importu danych, ponieważ pierwsze analizy wskazywały na użycie około 890MB – taka wartość była nie do przyjęcia. Standardowo na pierwszy ogień poszły obiekty DAO na rzecz zwykłych tablic – efektem był spadek do 280MB. Niemniej to nadal zbyt dużo, celem było maksimum 128MB. Analizując cały proces doszedłem do wniosku, że można przyspieszyć trochę proces i pozbyć się sporej ilości pamięci usuwając standardowe wyszukiwanie wierszy przez repozytorium obiektów DAO w zamian budując mini-repozytorim składające się z tablicy par (czyli tablica tablic de facto) [id, source_id]. Pierwsze testy i spore zaskoczenie, bo tak prosta struktura składająca się z około 65000 par zajmowała około 27MB. Poniżej przykład:

<?php

echo number_format(memory_get_usage(), 0, '.', ' ') . "\n";

$a=[];
for ($i=0; $i<65536; $i++)
{
	$a[] = [$i, 2*$i];
}

echo number_format(memory_get_usage(), 0, '.', ' ') . "\n";
echo number_format(memory_get_peak_usage(), 0, '.', ' ') . "\n";

daje w rezultacie:

Jest to wartością dość dużą, niestety. Zrobiłem szybko jeszcze dwa inne testy. Tablica jednowymiarowa z narzuconym indeksem:

<?php

echo number_format(memory_get_usage(), 0, '.', ' ') . "\n";

$a=[];
for ($i=0; $i<65536; $i++)
{
	$a[2*$i] = $i;
}

echo number_format(memory_get_usage(), 0, '.', ' ') . "\n";
echo number_format(memory_get_peak_usage(), 0, '.', ' ') . "\n";

dało w rezultacie:

oraz tablica jednowymiarowa bez narzuconego indeksu:

<?php

echo number_format(memory_get_usage(), 0, '.', ' ') . "\n";

$a=[];
for ($i=0; $i<65536; $i++)
{
	$a[] = $i;
}

echo number_format(memory_get_usage(), 0, '.', ' ') . "\n";
echo number_format(memory_get_peak_usage(), 0, '.', ' ') . "\n";

co dało wynik:

Różnica między drugim i trzecim testem choć znaczna, to i tak jest niewielka w porównaniu do pierwszego testu – zużycie pamięci jest prawie 10 razy mniejsze. Zacząłem się zastanawiać, co takiego tkwi wewnątrz tablic w PHP, że prosta struktura z niewielką ilością danych pochłania kosmiczne ilości pamięci?

Ściągnąłem więc źródła PHP w wersji 7.2.14 i po poszukiwaniach znalazłem powód.

Uwaga! Wszystkie wyliczenia prowadzone są z założeniem kompilacji kodu dla 64-bitowego procesora i systemu operacyjnego: typ long ma 8 bajtów, wskaźniki mają 8 bajtów.

Najpierw należy wyjaśnić, jak PHP wewnętrznie przechowuje zmienne. Zmienne w PHP z definicji nie mają typu, można string traktować jak integer i na odwrót (od PHP 7 mamy TypeHint, ale to jest inny mechanizm). Skąd więc wiadomo jakiego typu jest zmienna? Wewnętrznie odpowiada za to struktura ZVAL (zend_value) i wygląda ona tak:

struct _zval_struct {
	zend_value        value;			/* value */
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)	    /* call info for EX(This) */
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* literal cache slot */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     extra;                /* not further specified */
	} u2;
};

typedef union _zend_value {
	zend_long         lval;				/* long value */
	double            dval;				/* double value */
	zend_refcounted  *counted;
	zend_string      *str;
	zend_array       *arr;
	zend_object      *obj;
	zend_resource    *res;
	zend_reference   *ref;
	zend_ast_ref     *ast;
	zval             *zv;
	void             *ptr;
	zend_class_entry *ce;
	zend_function    *func;
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;

Nie będę dalej rozwijał wszystkich typów, tym bardziej że z ich nazwy można się zorientować, co tam jest. Generalnie struktura zend_value zawiera trzy pola, z których każde jest unią i ma długość:
• value – 8 bajtów
• u1 – 4 bajty
• u2 – 4 bajty

Wynika z tego, że sama struktura opisująca zmienną ma wielkość przynajmniej 16 bajtów, nie licząc wartości samej zmiennej: dla typów całkowitych i zmiennoprzecinkowych będzie to 0 (zero), ponieważ mieszczą się one bezpośrednio w strukturze; dla łańcuchów, tablic, obiektów itp. jest to tyle, ile wynosi rozmiar tej zmiennej – w strukturze jest przechowywany tylko wskaźnik do pamięci zajmowanej przez zmienną.

W naszym przykładzie było około 65000 liczb całkowitych, co daje 1,0MB na same liczby.

No dobrze, ale co w końcu z tymi tablicami? Tablice są opisane inną strukturą, zend_array:

struct _zend_array {
	zend_refcounted_h gc;
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    flags,
				zend_uchar    nApplyCount,
				zend_uchar    nIteratorsCount,
				zend_uchar    consistency)
		} v;
		uint32_t flags;
	} u;
	uint32_t          nTableMask;
	Bucket           *arData;
	uint32_t          nNumUsed;
	uint32_t          nNumOfElements;
	uint32_t          nTableSize;
	uint32_t          nInternalPointer;
	zend_long         nNextFreeElement;
	dtor_func_t       pDestructor;
};


typedef struct _Bucket {
	zval              val;
	zend_ulong        h;                /* hash value (or numeric index)   */
	zend_string      *key;              /* string key or NULL for numerics */
} Bucket;


typedef struct _zend_refcounted_h {
	uint32_t         refcount;			/* reference counter 32-bit */
	union {
		struct {
			ZEND_ENDIAN_LOHI_3(
				zend_uchar    type,
				zend_uchar    flags,    /* used for strings & objects */
				uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
		} v;
		uint32_t type_info;
	} u;
} zend_refcounted_h;

No i tablica okazała się całkiem rozbudowanym tworem. Mamy strukturę zend_refcounted_h z której korzysta GarbageCollector, mamy strukturę Bucket w której są poszczególne elementy tablicy no i w końcu strukturę samej tablicy, gdzie mamy rozmiar, wskaźnik bieżącego elementu itd.

Rozmiary poszczególnych części przedstawiają się następująco:
zend_refcounted_h – 8 bajtów
Bucket – 32 bajty (16 bajtów zval plus 2 pola po 8 bajtów)
zend_array – 56 bajtów (nie będę się tutaj rozpisywał, kto jest chętny może sobie sprawdzić)

Podsumowując: nasza tablica 65536 elementów typu całkowitego (integer) ma rozmiar:
• struktura zend_array: 56 bajtów
• 65536 elementów Bucket zawierających wartości integer: 2 097 120 bajtów

Razem daje to 2 097 176 bajtów, co jest w miarę zgodne z tym, co widzieliśmy w trzecim teście. Jak w takim razie wyglądałyby obliczenia dla pierwszego testu?

• struktura zend_array: 56 bajtów
• 65536 elementów Bucket zawierających tablicę: 2 097 120 bajtów
◦ struktura zend_array: 65536 * 56 bajtów = 3 670 016 bajtów
◦ 2 elementy Bucket zawierające liczby całkowite: 65536 * 2 * 32 bajty = 4 194 304 bajty

Łącznie daje to 9 961 496 bajtów. 10MB pamięci na dane, które w języku C zajęłyby maksymalnie 1MB to dość szokujące odkrycie. Tym bardziej, że podliczyłem same struktury danych, bez uwzględnienia faktu wyrównywania pól w strukturach do granicy 8 bajtów (biorąc to pod uwagę np. struktura zval_struct będzie zajmować w pamięci 24 bajty, nie 16; zend_array 80 bajtów zamiast 56) oraz samego zarządzania pamięcią w PHP, struktur pomocniczych itp.

W porządku, a co z obiektami? Cóż, są równie rozbudowane jak tablice:

struct _zend_object {
	zend_refcounted_h gc;
	uint32_t          handle; // TODO: may be removed ???
	zend_class_entry *ce;
	const zend_object_handlers *handlers;
	HashTable        *properties;
	zval              properties_table[1];
};

HashTable to alias dla zend_array (lepiej wygląda….), reszta jest wskaźnikami lub definicje są przedstawione wyżej, suma rozmiaru wszystkich pól daje 52 bajty (72 bajty uwzględniając memory alignment).

Cóż… Mit tak uwielbianych przeze mnie tablic legł w gruzach, tablice nie są dobre na wszystko i okazuje się, że bardzo łatwo jest naciąć się przy ich używaniu. Natomiast mając na względzie, jak są reprezentowane wewnętrznie w PHP można je efektywnie wykorzystywać – tablica składająca się z tablic o dużej liczbie elementów będących typami prostymi jest nadal jednym z najbardziej efektywnych sposobów przechowywania danych. Przypadek tablicy składającej się z dwu-elementowych tablic daje się na szczęście w prosty sposób zredukować do tablicy jednowymiarowej (indeks to jeden element, wartość to drugi).

Na zakończenie powiem jeszcze, że stosując kilka innych sztuczek (przetwarzanie w małych paczkach, korzystanie niemal wyłącznie z PDO, mini-repozytorium par danych) udało mi się zredukować zużycie pamięci z początkowego 890MB do 20MB; szybkość „przy okazji” też wzrosła z około 10 do 2 minut.

Autorem tekstu jest Łukasz Bugaj.

Jak długo to potrwa?

Estymacja

Za wikipedią estymacja to dział wnioskowania statystycznego, będący zbiorem metod pozwalających na uogólnianie wyników badania próby losowej na nieznaną postać i parametry rozkładu zmiennej losowej całej populacji oraz szacowanie błędów wynikających z tego uogólnienia.

Naturalnie nie mogę zagwarantować, że po lekturze niniejszego artykułu szanowny czytelnik będzie uzyskiwał dokładne estymacje. Niemniej mam nadzieję, że uczynię Cię bardziej pewnym w ich dostarczaniu.

Przykład

W artykule chciałem się skupić na przykładzie oraz metodologii, która sprawdza się w moim przypadku. Przechodząc do meritum proponuję rozpatrzyć następujący problem:

Jako administrator sklepu internetowego chcę, aby jego zamówienia zostały automatycznie przekazane po opłaceniu do zewnętrznego spedytora, który w tym celu wystawi niezbędne API.

Na pierwszy rzut oka brzmi prosto i precyzyjnie, niemniej w zależności od złożoności systemu oraz intensywności wpadania do niego zamówień sprawa znacząco się komplikuje. Widzimy zatem, że już na początku natrafiamy na pierwszą przeszkodę. Literatura elegancko nazywa niniejszy problem Cone of Uncertainty (czyli rożek/stożek/lejek niepewności). Termin ten oznacza, że developer w chwili początkowej startuje od największej ilości ryzyka i niepewności związanych z funkcjonalnością.

Sytuacja pogarsza się tym bardziej im zainteresowane strony zmieniają założenia i cele. Jedynym sposobem na obniżenia stożka jest stale przeprowadzanie badań oraz konsultacji w celu wyjaśnienia i uporządkowania możliwie jak największej ilości zmiennych.

Przy tak przedstawionym problemie naturalnie nasuwa się masę pytań. Jakim protokołem wysyłane będą dane do API, w jaki sposób będą zabezpieczone, jakiego formatu powinny być dane wyjściowe, jaką przepustowość zapewni bramka odbierająca zamówienia, itd…

Work-breakdown structure

Istnieje ogromna niepewność/nieprecyzyjność w naszej treści zadania. Pierwszą rzeczą, którą należy zrobić jest rozbicie zadania na mniejsze porcje pracy, tak abyśmy mogli wyestymować każde z osobna. Formalnie mówimy o tworzeniu work-breakdown structure (WBS), czyli strukturę podziału pracy.

Naturalnie po dokonaniu konsultacji z klientem otrzymałem niezbędną specyfikacje techniczną interfejsu API, który wymagał protokołu RESTowego z prostą autoryzacją tokenową. Specyfikacja również zawierała niezbędne szczegóły co do formatu danych wejściowych oraz zwracanych błędów. Dopiero wówczas problem mogłem podzielić na mniejsze komponenty:

1. przygotowanie interfejsu klienta, który przyjmując dane wejściowe konwertuje dane do formatu json i wysyła je bramki
2. dodanie do interfejsu klienta mechanizmu logującego wychodzące żądania oraz przychodzące odpowiedzi
3. przygotowanie mechanizmu kolejkującego zamówienia, aby w sytuacji niepowodzenia wysyłki móc ponawiać ją do skutku, a w sytuacji niedostępności serwisu partnera poinformować o tym administratora systemu
4. przygotowanie serwisu mapującego dane zamówienia do modelu przyjmowanego przez bramkę partnera
5. implementacja wysyłki danych

Naturalnie wciąż pozostaje sporo niejasności zwłaszcza w sytuacji metodologii mapowania. Po dalszych konsultacjach okazało się, że administrator systemu zarządał dodatkowej kontroli nad mapowaniem w związku z tym ostatni punkt musiałem rozbić na jeszcze mniejsze partie:

4a. przygotowanie GUI dla elementów zamówienia, które administrator sytemu może zmapować na dane zgodne w bramce partnera
4b. przygotowanie struktury przechowującej relacje encji naszego systemu do encji systemu partnera
4c. uzupełnienie mappera zamówień o zaktualizowane właściwości oraz encje systemu partnera zgodnie z zapisaną mapą

W powyższym podziale zawsze staram się trzymać zasady, aby każdy z punktów pochłonął do 4h mojej pracy. Uwaga! podejrzewam, że wielu programistów spotkało się w literaturze, aby porcje zajmowały nie więcej niż 8h, czyli maksymalnie jeden dzień roboczy. Istnieje również Queueing theory (teoria kolejek), która sugeruje rozbijanie rzeczy na mniejsze, łatwiejsze do opanowania części, dzięki czemu istnieje możliwość równoległego przepływu pracy co w konsekwencji prowadzi do zwiększenia jej przepustowości.

Niemniej ze względu na fakt, że często pracuje nad zadaniami samodzielnie i jestem zmuszony przerywać prace i zajmować się innymi drobniejszymi tematami, które wpadają niezapowiedzianie jak konsultacje lub hotfixy, drobniejsza estymacja pozwala mi lepiej planować sobie nadchodzący dzień. Naturalnie w sytuacji gdy skończę bieżący punkt zadania, biorę się za następny 😉

Naturalnie finalną estymacją jest zsumowanie czasów wykonania poszczególnych punktów. Powyższy przykład przedstawia tzw. oszacowanie eksperckie. Oprócz tego istnieje jeszcze podejście formalne, tj. wykorzystanie próby statystycznej. Dzięki niej można obliczyć dodatkowy błąd statystyczny i uwzględniać go w następnych szacunkach. Oraz dysponujemy również podejściem łączonym, które stanowi sumę dwóch wspomnianych metod.

Planowanie grupowe

W sytuacji, gdy poszczególne punkty naszego zadania szacujemy w grupie programistów dysponujemy jeszcze takimi opcjami jak Planning Poker (lepiej znany jako Scrum Poker) oraz Wideband Delphi. W pierwszej, członkowie grupy dokonują oszacowań, grając ponumerowanymi kartami zakrytymi do stołu, zamiast wypowiadać je na głos. Karty zostają ujawnione, a następnie omawia się dane szacunkowe. Ukrywając dane w ten sposób, grupa unika nastawienia poznawczego, gdzie pierwsza liczba wypowiedziana na głos stanowi precedens dla kolejnych oszacowań.

W procesie Delphi koordynator zespołu przedstawia każdemu ekspertowi specyfikację i formularz oceny. Następnie podczas grupowego spotkania eksperci omawiają kwestie oceny z koordynatorem i między sobą. Na koniec wypełniają formularze anonimowo, a Koordynator przygotowuje i rozpowszechnia podsumowanie szacunków. W sytuacji gdy estymacje są mocno rozbieżne powtarzamy cały proces, aż do osiągnięcia zadowalającego konsensusu.

Niewątpliwą zaletą wspólnego szacowania problemów programistycznych jest fakt, że te mogą odkryć nowe techniki i/lub pułapki które skrywa zadanie. Istnieje jeszcze bardziej złożone podejście mianowicie metoda punktów przypadków użycia (Use Case Points w skrócie UCP). Polega ona na przypisywaniu punktów relatywnie w zależności od złożoności zadania. Im bardziej złożone tym kosztują więcej punktów. Pomysł polega na tym, że bardziej złożone zadanie powinno się wykonać dłużej, niż proste zadanie. Po uzyskaniu punktacji można w pewien sposób uzyskać niezbędną ilość godzin developerskich. Niemniej należy pamiętać, że punkty nie stanowią bezpośredniego przełożenia na liczbę godzin. Dla zainteresowanych odsyłam do artykułu: https://rubygarage.org/blog/3-reasons-to-estimate-with-story-points

Podsumowanie

Podsumowując, estymacje stanowią istotną część cyklu produkcyjnego. Pozwalają nam podejmować decyzje dotyczące tego, co się dzieje w projekcie. Na koniec zwracam jeszcze uwagę o prawie Brook’a z pozycji „The Mythical Man Month”, który stwierdza, że dodawanie osób do projektu może zwiększyć ilość czasu w jego realizacji. Zatem należy również pamiętać o równowadze między wprowadzaniem ludzi w zadania bez powodowania wielu przeszkód i utrudnień.

Autorem tekstu jest Marek Rode.

Czy jesteśmy bezpieczni w sieci?

Niedawno uczestniczyłem w szkoleniu traktującym o cyberbezpieczeństwie i cyberzagrożeniach. Zajęcia, w których uczestniczyłem skupiały się np. na „cichym” skanowaniu sieci LAN, wykorzystaniu innych komputerów do skanowania sieci (czyli tak, aby nie pozostawiać śladów), wykorzystaniu socjotechniki w celu uzyskania informacji czy dostępu do stanowisk komputerowych, a głównie na wykorzystaniu tych informacji do przejęcia kontroli nad zaatakowanym komputerem. W trakcie trwania tego szkolenia naszło mnie kilka myśli, którymi chciałbym się z Tobą, drogi Czytelniku, podzielić.

Czy jesteśmy bezpieczni w sieci?

Pierwszy dzień szkolenia skupiał się głównie na odnajdywaniu w sieci komputerów ale w taki sposób, aby oszukać systemy IDS/IPS. Czym są takie systemy? Otóż mają one wykrywać (IDS) i blokować (IPS) ataki sieciowe. Jak działają? W skrócie: analizują pakiety krążące w sieci pod kątem np. wystąpienia znanych sygnatur ataków (analiza sygnaturowa) bądź wykrywaniu anomalii w ruchu sieciowym (np. jeden komputer zaczyna rozsyłać pakiety do innych komputerów w danej sieci). W połączeniu z dobrze skonfigurowanym firewallem stanowią niezłe podstawy do zwiększenia bezpieczeństwa w sieci.

Jedną z metod próbujących oszukać te systemy jest zbudowanie sobie własnego pakietu (drugiej, trzeciej lub czwartej warstwy modelu OSI) i wysłanie go do sieci. Jeden taki pakiet nie jest żadną anomalią ani nie zawiera złośliwych nagłówków, więc systemy bezpieczeństwa raczej go zignorują. Natomiast umiejętne połączenie takiej metody z jakimś narzędziem do automatyzacji może dać nam przepiękny obraz tego, jak wygląda skanowana sieć. Możemy poznać jakie hosty są włączone, jakie usługi są na nich zainstalowane i w jakich wersjach (co jest istotne do skutecznego przeprowadzenia ataku na daną maszynę).

A dlaczego tytułowa myśl pojawiła się w mojej głowie? Otóż na szkoleniu mieliśmy przygotowane dobre warunki laboratoryjne. Sala, w której szkolenie się odbywało była izolowana od pozostałej części sieci w budynku, abyśmy spokojnie mogli atakować każdą znalezioną maszynę (bo – przynajmniej w teorii – każdy znaleziony host powinien być albo naszym komputerem albo przygotowanym przez prowadzącego komputerem „ofiarą”). Niektórym uczestnikom udało się jednak tak przeskanować sieć, że znaleźli drukarkę której znaleźć nie powinni. Kilka chwil później mieliśmy już dostęp do każdego innego komputera z budynku, gdyż ta drukarka posłużyła nam jako „pomost” do reszty świata.

Nawet jeśli administrator „poprawnie” skonfigurował swoją sieć izolując pewne jej części, separując je od innych sieci w celu ograniczenia możliwości ataków (dobrym przykładem może tu być np. biuro kadr czy księgowej, gdzie są wrażliwe dane firmowe i nikt nie powinien mieć do nich dostępu) nie zawsze jest w stanie przewidzieć, jakie nowe zagrożenia mogą się pojawić.

Czy warto aktualizować oprogramowanie?

Dlaczego udało nam się za pomocą drukarki wydostać z izolowanej sieci? Przede wszystkim oprogramowanie drukarki było dawno nieaktualizowane. Znalezienie odpowiedniego exploita też nie było żadnym problemem, gdyż drukarka przedstawiła nam się pięknie z imienia, modelu i konkretnej wersji oprogramowania. Działała też ona jako serwer wydruku w firmie i przez niewłaściwą konfigurację była dostępna w każdej sieci.

No dobrze, teraz powiesz mi: „W takim razie odłączę drukarkę od sieci i będę używał starego, dobrego USB. Będę bezpieczny?”. Ja odpowiem: „NIE”*.

Skanując sieć dalej odnalazłem komputer działający pod kontrolą systemu z nadgryzionym jabłkiem, na którym mieliśmy odpaloną prezentację multimedialną (dodam, że był on na bieżąco aktualizowany). Wiedziałem, że włamanie się do tego komputera nie będzie łatwe (o ile w ogóle możliwe na tym etapie na którym jestem). Jednak kilka narzędzi do których miałem dostęp wskazały mi kilka wektorów ataku. Wykorzystując jeden z nich udało mi się nawiązać połączenie „z ofiarą”. Niestety, niedostateczna jeszcze wtedy wiedza nie pozwoliła mi nic z takim połączeniem zrobić. Kolejnego dnia szkolenie rozpoczęło się z lekkim poślizgiem, bo MAC OS wymusił aktualizację systemu. Byłem ciekaw, jaki to miało wpływ na sytuację z dnia poprzedniego. Co się okazało? Po aktualizacji nie byłem już w stanie nawet nawiązać połączenia z tym komputerem. Dziura, którą udało mi się wykorzystać poprzedniego dnia w tej aktualizacji została załatana. Komputer był bezpieczny. Teraz ponownie zadasz mi pytanie: „Świetnie! W takim razie jeśli będę aktualizował swoje oprogramowanie mogę czuć się bezpiecznie?”. Ja znów odpowiem: „NIE”*.

Czy warto stosować programowy firewall i antywirus?

Dlaczego NIE? To, że dziś nie mogę się włamać do Twojego komputera nie oznacza, że jutro ktoś nie znajdzie nowej podatności. Pojutrze znów zaktualizujesz komputer, a za tydzień znów będzie podatny na ataki. Nie mówi się o tym wiele, ale istnieje nieustanny wyścig producentów oprogramowania z grupami, które to oprogramowanie próbują złamać. Można natomiast wspomagać systemy operacyjne stosując dodatkowe narzędzia chroniące nasze dane.

Ostatni dzień szkolenia pokazywał, jak używając narzędzi socjotechnicznych można włamywać się na komputer ofiary. W wielu wypadkach włamanie odbywa się za pomocą pliku, który stanowi nośnik exploita, i został przygotowany tak, by udawać ważny załącznik do wiadomości. Wystarczy, że ofiara uruchomi go na swoim komputerze i pozamiatane. Brzmi znajomo? Ile razy słyszało się o atakach, które wyglądały tak: „W załączniku wysyłamy zaległą fakturę. Proszę ją opłacić gdyż już jest 3 dni po terminie!!!”. A w załączniku co? Plik zip z dokumentem pdf, który po odpaleniu albo szyfruje nam cały dysk (co jest od razu zauważalne) albo co gorsza rzeczywiście wyświetli nam jakąś fakturę (uff… na szczęście to nie do mnie, więc nie muszę się martwić), ale OD RAZU I NATYCHMIASTOWO DA DOSTĘP do komputera ofiary (na prawach użytkownika, który uruchomił wirusa – ale to też da się obejść).

No dobrze, ale dlaczego nagłówek akapitu pyta o antywirusy? Już spieszę z odpowiedzią. Przygotowane przez prowadzącego maszyny, które mogliśmy atakować były jak najbardziej „gołe” (bez ostatnich aktualizacji, bez antywirusów czy innych dodatkowych zabezpieczeń). Ale ja nie byłbym sobą, gdybym nie spróbował zaatakować swój własny komputer (Windows 8.1, wszystkie aktualizacje + zewnętrzny program antywirusowy). I powiem Ci czytelniku jedno: żeby świadomie i z premedytacją zawirusować mój własny komputer musiałem godzinę walczyć z moim antywirusem, który za żadne skarby (nawet po wyłączeniu wszystkich aktywnych ochron) nie chciał mi pozwolić na tak głupi manewr. W końcu mi się to udało, ale czynności, które musiałem wykonać dały jednoznaczną odpowiedź na pytanie z nagłówka. TAK – jak najbardziej warto używać zewnętrznego programu antywirusowego. I niech on sobie przez cały rok subskrypcji leży w trayu i nie zgłasza żadnego zagrożenia. Wystarczy jedna udaremniona próba ataku na komputer, aby docenić wartość takiego oprogramowania.

A co z mechanizmami zabezpieczającymi dostarczanymi przez system?

Na zakończenie wspomnę o jeszcze jednej sytuacji ze szkolenia. Mieliśmy tam chwilę, aby pobawić się z nowym narzędziem służącym do exploitacji systemów rodziny Windows. Narzędzie to (w dniu szkolenia) pozwalało „zarządzać” komputerami pracującymi pod kontrolą systemów Microsoftu od 7 w dół. Ja oczywiście spróbowałem go użyć na moim Win8.1. O dziwo (wbrew zapewnieniom prowadzącego) udało mi się podnieść uprawnienia uruchomionej aplikacji omijając mechanizm UAC (w zasadzie nie omijając, ale odpowiednio modyfikując komunikat proszący o podniesienie uprawnień). Bo powiedzmy sobie szczerze: kto czyta wszystkie okna systemowe? Podejrzewam, że nawet gdyby tam było wielkimi literami napisane: „CZY ZGADZASZ SIĘ, ABY TA APLIKACJA UZYSKAŁA UPRAWNIENIA ADMINISTRATORA W CELU WŁAMANIA SIĘ DO TEGO KOMPUTERA?” – mało kto by to przeczytał. Niestety rutyna, która wkrada się podczas codziennego używania komputera skutecznie znieczula nas na komunikaty, które pojawiają się na ekranie.

Co do samego UAC to prowadzący zarzekał się, że jeszcze tydzień wcześniej na poprzednim szkoleniu nikomu się ta sztuczka nie udała. Od razu wypróbowaliśmy to na komputerach z Windowsem 10. Efekt był zdumiewający – nie dość że udało się wyświetlić na ekranie zmodyfikowane okno UAC to po kliknięciu na „TAK” dostaliśmy pełny dostęp do zaatakowanego komputera na prawach ADMINISTRATORA. Strach pomyśleć, co z takim dostępem cracker by mógł zrobić.

Podsumowanie

Ze szkolenia z cyberhackingu wróciłem z uczuciem przerażenia. Mnogość narzędzi wspomagających ataki oraz metody ataków ewoluują po każdej aktualizacji oprogramowania. Świadomość, że są ludzie posiadający podobną (a wręcz dużo większą) wiedzę, ale wykorzystujący ją do złych celów nie napawa optymizmem. Wszak codziennie w spamie ląduje przynajmniej kilka wiadomości sugerujących: „Dopłata do faktury 1zł” czy „Wygrałeś najnowszego iPhona X – kliknij tutaj aby odebrać nagrodę”. Natomiast wiedza o tym, jak wygląda taki atak „z drugiej strony” pozwala mi lepiej zabezpieczyć swój sprzęt a także sieci, którymi się opiekuję lub będę się kiedyś opiekował.

Bo jeśli chcielibyśmy mieć w 100% bezpieczny i wolny od zagrożeń komputer należałoby go nigdy nie podłączać do sieci. Ale jeśli już musimy, to należy mieć na uwadze kilka rzeczy:
1. W każdym momencie jesteśmy narażeni na ataki internetowe.
2. MOŻEMY z wielką skutecznością bronić się przed nimi.
3. Najsłabszym ogniwem podczas ataków jest użytkownik (bo źle skonfigurował usługę, bo dał się podejść atakowi socjotechnicznemu, bo WYŁĄCZYŁ antywirusa).
4. Zwiększając swoją świadomość na temat cyberzagrożeń podnosimy swoją skuteczność na ataki.

Wracając jeszcze na chwilę do udzielanych przeze mnie odpowiedzi na poszczególne pytania z nagłówków chciałbym dodać: nie jesteśmy w pełni bezpieczni w sieci, ale możemy z wielką skutecznością chronić swój komputer. Warto (a wręcz trzeba) aktualizować oprogramowanie. Nie tylko w komputerach, ale też (a może zwłaszcza) w innych urządzeniach podpiętych do sieci (drukarkach, konsolach, lodówkach (sic!)).
Aktualizacje bezpieczeństwa wydawane są właśnie po to, by owo bezpieczeństwo podnosić. Wskazane jest też zainstalowanie na swoim komputerze jakiegoś programu antywirusowego. Jeśli nie stać Cię na komercyjne rozwiązanie jest wiele darmowych programów. Z uwagą należy także czytać wyskakujące komunikaty systemowe. Nie każdy uruchomiony program powinien działać na prawach administratora (jeśli Twój nowy, świeżo pobrany super-kalkulator tego wymaga to warto zastanowić się, czy nie lepiej użyć innego?).

PS W tekście celowo nie podaję nazw oprogramowania. Niektóre sytuacje zostały też „nagięte” lub uproszczone na potrzeby napisania tego tekstu.

PPS A taką wiadomość dostałem od sąsiada po powrocie ze szkolenia:

Autorem tekstu jest Łukasz Wojtyczka.

PWA (Progressive Web App)

Zacznijmy od wykresu pokazującego statystyczną ilość pobieranych aplikacji na telefon przez użytkowników smartfonów:

1

Średnia miesięczna Liczba pobranych aplikacji przez użytkowników smartfonów (źródło: Statista 2018)

Wniosek? Ponad połowa użytkowników smartfonów nie pobiera aplikacji na telefon. Skąd taki wynik? Większość z nas korzysta tylko z kilku popularnych aplikacji natywnych, natomiast w stosunku do pozostałych stron chętniej korzystamy z witryn webowych. Dlatego właśnie mówimy o PWA (Progressive Web App).

PWA jest więc aplikacją webową, która dzięki kilku modyfikacjom, naśladuje działanie aplikacji natywnej. Zasady tworzenia aplikacji PWA zgodnie z checklistą Google:

  • Korzystamy z https, w celach bezpieczeństwa,
  • Strona musi być responsywna (RWD – responsive web design),
  • Działa w trybie offline (dzięki wykorzystaniu Service Worker),
  • Posiada web app manifest opisany w pliku manifest.json, który zawiera informacje o aplikacji (nazwę, ikonki itp) odpala się na starcie aplikacji,
  • Ładuje się szybko, nawet przy wolnych połączeniach (3G),
  • Działa na różnych przeglądarkach,
  • Płynnie reaguje na aktywność użytkownika (np. przy przejściu na kolejną stronę),
  • Każda strona posiada adres url (możliwość odtworzenia stanu aplikacji z podanego adresu url)

W tym artykule chciałam skupić się na dwóch istotnych zagadnieniach z powyższej checklisty – Service Worker oraz Web app manifest.

W celu pokazania aspektów technicznych posłużę się przykładem tworzenia aplikacji PWA z wykorzystaniem Angulara 6.

Web app manifest – manifest.json

Przykład:

{
  "name": "angular-pwa",
  "short_name": "app-pwa",
  "theme_color": "#1976d2",
  "background_color": "#fafafa",
  "display": "standalone",
  "scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "assets/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    }
  ]
}

Co mówi nam powyższy plik?

{
  "name": "angular-pwa",
  "short_name": "app-pwa"
}

Nazwa aplikacji to „angular-pwa”, natomiast skrócona nazwa „app-pwa” pokaże się w miejscach gdzie do dyspozycji mamy ograniczoną przestrzeń np. pod ikoną na pulpicie urządzenia.

Gdy użytkownik doda naszą aplikację do ekranu głównego, możemy zdefiniować zestaw ikon do użycia w przeglądarce (w takich miejscach jak ekran główny, menu, ekran powitalny, itp). Ikony to tablica obiektów zawierająca ścieżkę src, rozmiar, typ obrazu.

{
 "icons": [
    {
      "src": "assets/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "assets/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
 ]
}

start_url informuje przeglądarkę, gdzie powinna uruchomić się nasza aplikacja. Powinna być skierowana bezpośrednio do naszej aplikacji.

{
"start_url": "/"
}

Pozostałe informacje takie jak:

{
 "theme_color": "#1976d2",
  "background_color": "#fafafa",
  "display": "standalone",
  "scope": "/"
}

opisują kolor aplikacji, motyw tzw. splash screenu oraz sposób wyświetlania aplikacji. Właściwość „standalone” pokazuje aplikację w sposób natywny tzn. aplikacja działa we własnym oknie, niezależnie od przeglądarki i ukrywa standardowe elementy interfejsu użytkownika przeglądarki, takie jak pasek adresu URL itp.

Co najważniejsze nasz manifest.json, musi być dodany do naszej aplikacji. Po pierwsze do pliku index.html do sekcji head:

{
  <link rel="manifest" href="manifest.json">
}

Oraz do pliku angular.json w sekcji assets:

{
"assets": [
      "src/manifest.json"
    ]
}

Service Worker

Działanie Service Workera jest obszernym tematem, jednak to co nas interesuje można podsumować następująco:

Service Worker to skrypt działający w przeglądarce, który pełni rolę proxy pomiędzy aplikacją kliencką, a aplikacją serwerową. Przechowuje żądania sieciowe, aktualizuje zasoby znajdujące się na serwerze, określa które zasoby zapisujemy do cache’a, tak aby użytkownik przy kolejnym uruchomieniu aplikacji nie musiał czekać na pobranie tych danych z serwera. Service Worker pozwala także na implementację push notifications.

Jak dodać PWA do naszej aplikacji w Angularze?

Wykorzystując komendę angular cli wpisujemy w consoli:

ng add @angular/pwa --project *project-name*

Powyższa komenda dodaje @angular/service-worker do naszej aplikacji, importuje i rejestruje service worker w naszym app-module, aktualizuje plik index.html dodając manifest.json oraz tworzy plik konfiguracyjny ngsw-config.json, który zawiera mechanizm cache’owania aplikacji.

Przykładowy plik ngsw-config.json:

{
  "index": "/index.html",
  "assetGroups": [{
    "name": "app",
    "installMode": "prefetch",
    "resources": {
      "files": [
        "/favicon.ico",
        "/index.html",
        "/*.css",
        "/*.js"
      ],
      "urls": [
        "https://fonts.googleapis.com/css?family=Oswald:300,700",
        "https://fonts.gstatic.com/**"
      ]
    }
  }, {
    "name": "assets",
    "installMode": "lazy",
    "updateMode": "prefetch",
    "resources": {
      "files": [
        "/assets/**"
      ]
    }
  }],
}

W powyższej sekcji assetGroups mamy dwa obiekty app i assets. W pierwszym obiekcie konfigurujemy cache’owanie głównego pliku index.html, plików css, js. Natomiast drugi dotyczy cache’owania zasobów z folderu assets.

{
  "dataGroups": [
    {
      "name": "posts",
      "urls": [
        "https://jsonplaceholder.typicode.com/posts"
      ],
      "cacheConfig": {
        "maxSize": 5,
        "maxAge": "6h",
        "timeout": "10s",
        "strategy": "freshness"
      }
    }
  ]
}

Sekcja dataGroups określa mechanizm cache’owania wywołań z API.

Jak sprawdzić czy nasze PWA działa?

Najpierw musimy zbudować nasz projekt produkcyjnie

ng build --prod

Standardowa komenda ng-serve nie wspiera działania service-workera, dlatego aby uruchomić naszą aplikację PWA wykorzystamy http-server.

Instalujemy http-server za pomocą:

sudo npm install http-server -g

następnie, wpisujemy poniższą komendę:

http-server -p 8080 -c-1 dist/*project-name*

Projekt uruchamia się jako wersja produkcyjna na porcie 8080 (flaga c-1 wskazuje folder gdzie mieści się nasza zbudowana aplikacja).

Co otrzymujemy?

tech1

A co najważniejsze, wchodząc w tryb offline możemy zauważyć, że service worker działa. W kolumnie „size” widzimy, że zasoby pobierane są z service workera:

tech2

Podsumowanie

Podsumowując PWA jest zdecydowanie pożądaną funkcjonalnością i dużym ułatwieniem dla użytkowników smartfonów, tabletów jak i również developerów. Pisząc jedną aplikację webową tworzymy aplikację uruchamianą na wszystkich platformach (Desktop, iOS, Android), zarządzaną jednym wspólnym kodem.

Źródło:
1. https://developers.google.com
2. https://angular.io

Autorką tekstu jest Klaudia Moroń.

Wykorzystanie PHP bez frameworka?

To nie jest tyrada anty-frameworkowa…

Uwaga! To nie jest tyrada anty-frameworkowa ani tym bardziej promowanie nieszablonowego myślenia. Dla wielu czytelników bloga temat jest z pewnością znany. Na czacie Stack Overflow PHP można odnaleźć wielu developerów pytających czy framework X jest dobry, czy może Y lepszy… W większości przypadków odpowiedź brzmiała, że powinni używać PHP, a nie architektury do budowania aplikacji.

W niniejszym wpisie chciałbym zachęcić Ciebie, szanowny czytelniku, abyś dał sobie szansę na rozwój jako programista. W większości przypadków struktura aplikacji jest tak rozbudowana, że często traci swój sens. Natomiast pisanie aplikacji od zera przy pomocy open-sourcowych pakietów jest znacznie łatwiejsze, niż niektórzy myślą. Pod niektórymi względami wykorzystanie zaawansowanych frameworków jest dobre, niemniej cała nadreprezentacja obiektów jest przytłaczająca i może przeciwdziałać jego intencjom i stać się znaczącym drenażem wydajności. Dawno temu skupialiśmy się na ważnych rzeczach, czyli rozwiązywaniu problemów i logice biznesowej. Dzisiaj jesteśmy prawie całkowicie skoncentrowani na abstrakcji i generalizacji. Podczas gdy ważne obiektywnie rzeczy, które opłacają rachunki, otrzymują tylko resztki czasu, jeśli w ogóle takowe pozostają. Zwykle spędzaliśmy 90% czasu, decydując o właściwym gwoździu do użycia, odpowiednim drewnie, na którym go używamy, i gdzie najlepiej go umieścić, i tylko 10% naszego czasu na dobór i posługiwanie się młotkiem. Teraz spędzamy 90% czasu zastanawiając się, który FactoryAbstractManagerFactoryGenericInterfaceTool wywołać i jak poprawnie go użyć w taki sposób, aby zmusić go do ostatecznego wygenerowania czegoś, co ostatecznie może wygenerować coś innego i utrzyma całość razem. Zmusza to również pozostałych członków zespołu deweloperskiego do intensywnego debugu, dlaczego niniejszy obiekt został użyty, w jakim celu oraz co będzie efektem jego użycia.

Czy przesadzam? Możliwe, że odrobinę… programiści uchodzą za inteligentnych ludzi. Ustalają jak wykorzystać złożone kombinacje systemów do budowania funkcjonalnych, monstrualnych i monolitycznych aplikacji. Ale czy naprawdę redukujemy dług techniczny? Czy w rzeczywistości go dodajemy? Czy te systemy naprawdę będą łatwiejsze do utrzymania w ciągu kilku lat, gdy będą mieć kilkaset zależności, z których każda ewoluowała w czasie lub co gorsza została porzucona? Czy łatwiej przyjdzie zoptymalizować kod, kiedy oznacza to modyfikację tego, co dzieje się pod maską każdej z tych zależności? Czy łatwiej będzie je rozszerzyć, prowadząc do konfliktów z tymi zależnościami?

W związku z powyższym problemem może okazać się, że kolejna fala odkryć i szumu w tej dziedzinie skupi się na poprawie produktywności przy jednoczesnym zmniejszeniu zadłużenia technicznego poprzez usunięcie narzędzi, które teraz budujemy (lub wykorzystujemy) i zastąpienie ich czymś prostszym i bardziej precyzyjnym.

Być może największą korzyścią z pracy bez ram frameworka jest bogactwo wiedzy o tym, co dzieje się pod maską. Widać dokładnie, co się dzieje, bez polegania na jego magii, aby zająć się rzeczami w taki sposób, żeby nie było potrzeby debugować kodu i naprawdę go zrozumieć.

Niestety ramy niniejszego wpisu nie pozwalają mi przedstawić przykładu aplikacji bez frameworka, niemniej posłużę się garścią informacji, które najprawdopodobniej wszyscy developerzy znają, ale niestety zdążyły umknąć. PHP już od wersji 5 zawiera wiele struktur, których szukamy w zewnętrznych bibliotekach, a które są dostępne w ramach standardowego rozszerzenia:

Podsumowanie

Reasumując, framework stara się zaoszczędzić pracę, poprzez zdefiniowanie kompleksowej i czasochłonnej konfiguracji, która musi uwzględniać wszystkie możliwe przypadki użycia. To deklaratywne podejście do budowania aplikacji nie zawsze jest lepsze niż programowe, gdzie ręcznie tworzy się kolejne zależności obiektów. Czy jest to powód, by porzucić framework? Tylko wtedy, gdy już wiesz, że twoja aplikacja nie będzie potrzebować tak złożonej architektury. Inspiracją do stworzenia tego wpisu była książka: https://github.com/PatrickLouys/no-framework-tutorial

Autorem tekstu jest Marek Rode.