Docker i iptables

Ostatnio stanąłem przed problemem zablokowania dostępu do sieci dla kontenerów Dockera do jednego z moich projektów. Zadanie wydawało mi się trywialne, ale jakoś tak się złożyło, że strasznie je sobie zagmatwałem. Koniec końców rozwiązanie sprowadza się do jednej linijki:

iptables -A DOCKER-USER -j DROP

Jednak zanim do tego dotarłem to musiałem całkiem sporo dowiedzieć się jak działa sieć Dockerowa.

Wracając do początku, celem jest zablokowanie ruchu do Internetu, sieci lokalnej i pomiędzy kontenerami z wewnątrz systemu gościa. Gospodarz ma posiadać dostęp do usług, które serwuje kontener dockerowy. Pierwszym pomysłem jaki mi wpadł to ustawienie osobno firewalla na każdym z obrazów (przebudowa Dockerfile). Jest to karkołomne rozwiązanie. Poza czasochłonnością tego podejścia, zatrzyma nas w tym wszystkim komunikat:

iptables can’t initialize iptables table `filter’: Permission denied

tak, nawet jako root! Pewnym rozwiązaniem jest doinstalowanie sudo i utworzenie użytkownika po czym przelogowanie się na niego. Trzeba jeszcze pamiętać o uruchomieniu dockera z wywołaniem –cap-add=ALL…lecz, nigdzie w oficjalnej dokumentacji nie znalazłem wzmianek, że taka droga jest zalecana.

Wydaje się więc, że najlepiej sterować zachowaniem zapory sieciowej z poziomu systemu gospodarza, przy użyciu dostarczonych łańcuchów (docker, docker-user etc.).

Więcej na ten temat w
https://sowhatisthesolution.wordpress.com/2018/09/11/protect-docker-from-internet-while-allowing-lan-with-iptables/
i https://docs.docker.com/network/iptables/

ahh i jeszcze, jeśli to rozwiązanie Ci nie pomogło, zobacz swoje aktualne reguły (iptables -nL), być może posiadasz jakieś reguły zezwalające (można je nadpisać, zamiast parametru -A użyj -I).

Sprawna sylwetka

“Spójrzmy prawdzie w oczy: my, programiści, spędzamy większość swojego czasu w pozycji siedzącej przy biurku. Dlatego tak ważne jest, abyśmy mieli odpowiednią wiedzę na temat tego, co trzeba robić, żeby żyć zdrowo. Niestety nasza praca popycha nas w przeciwnym kierunku”

John Sonmez, “Sprawny programista”

Muszę przyznać, że długo zwlekałem z tym wpisem. Głównie z racji tego, że jest to kwestia szczególnie ważna dla mnie, ale jednocześnie wciąż bardzo mglista, niestety. Zdaje sobie też sprawę, że niemożliwym jest naświetlenie tego tematu jednym wpisem to też chciałbym rozpocząć na moim blogu serie artykułów o dbaniu o własną kondycję. To co już wiem będzie uzupełnione o to co napisali autorzy tj. cytowany już John Sonmez w “Sprawny programista” i Tomasz Saweczko w “Najpierw wiedza, potem rzeźba” (szczególnie polecam tą drugą książkę, bo jest w niej zawarta spora dawka konkretnej wiedzy, w dodatku opisana bardzo prostym, przystępnym językiem). Mam nadzieje, że coś z tego wyjdzie 😉

Nie da się ukryć, że codzienna praca przy komputerze niszczy nasze zdrowie, kondycje i po pewnym czasie samoocenę oraz samopoczucie. Dopadło to również i mnie. Choć staram się regularnie być w “ruchu”, trochę pływać, trochę biegać to jednak zaczynam na własnej skórze odczuwać rezultaty “pracy przy biurku”. To właśnie z tego powodu stale poszukuje sposobów na poprawę własnej sylwetki. Nie jest to proste. Właściwie to ciągła walka z własnymi nawykami, słabościami czy błędnymi przekonaniami. Jednakże mam też swoje własne, małe zwycięstwa. W tym wpisie przedstawię co się takiego u mnie zdarzyło, że w zeszłym roku zrzuciłem 8kg mimo pracy za biurkiem (a wpis, że w tym roku z kolei przytyłem 8kg będzie następnym razem :D).

Pierwsza zasada: Licz kalorie
Za namową autora “Najpierw wiedza, potem rzeźba” zacząłem prowadzić w ekselu dziennik tego ile kalorii dziennie dostarczam do swojego organizmu. Nie jest to zadanie jakoś wybitnie trudne, ale wymaga od nas pewnej samodyscypliny. Po pewnym czasie sami zapamiętujemy ile dane składniki mają kalorii, zatem robimy to już automatycznie. W tym miejscu uwaga, nie należy liczyć kalorii z dużą dokładnością, a wziąć pewien margines błędu. Oczywiście wcześniej musimy zmierzyć ile kalorii potrzebuje nasz organizm, tak by otrzymać różnice 7000 kcal potrzebnych do zrzucenia 1kg!

Druga zasada: Jedz tylko pełnowartościowe posiłki
Pełnowartościowe to takie, które zawierają dużo błonnika, minerałów, witamin, produktów pełnoziarnistych czy zdrowych tłuszczy. Długo nie mogłem się przekonać do większego spożywania warzyw we własnej diecie. Głównie za sprawą, że jadłem te warzywa, które mi nakazano. Tu jednak kluczem jest znalezienie takich, które Tobie smakują (na początku nie ma co przejmować się proporcjami w bilansie makroskładników, czy liczeniem kalorii dla zjedzonych warzyw).

Trzecia zasada: 10% Twojej diety, to musi być uczta dla duszy
Szczerze mówiąc nie dałbym rady utrzymać swojej diety, gdyby nie było w niej kilku produktów, za którymi szaleje, a które nie można zaklasyfikować jako “pełnowartościowe“. Oczywiście, musimy je dawkować z umiarem tak by nasycić duszę i mieć siłę do dalszej walki.

Czwarta zasada: Mierz swoją efektywność
No właśnie, nic tak nie motywuje człowieka do dalszego działania jak efekty z własnej pracy. Skrupulatne mierzenie wagi czy wykonywanie co pewien czas pomiaru składu ciała pomaga w konsekwentnym dążeniu do celu.

Piąta zasada: Trenuj to co lubisz
Nie trzeba chodzić na siłownie by zrzucić nadmierne kilogramy. Ważny jest ruch, nawet zwykły spacer na wieczór jest lepszy niż leżenie przed telewizorem. Natomiast to co mnie najbardziej zmotywowało do cotygodniowego treningu na basenie to fakt, że naprawdę to mnie relaksuje ;-).

Do tego dodałbym jeszcze, że oprócz diety, treningu, motywacji i regeneracji warto zatroszczyć się o wiedzę, czyli co i jak należy robić by osiągnąć upragnioną sylwetkę.

Ja walczę dalej i mam nadzieje, że uda mi się zebrać dla Was kolejne owocne spostrzeżenia 😉

Nowości w PHP 7.4

Premiera nowej wersji PHP 7.4 w listopadze, ale myślę, że warto już teraz zastanowić się nad tym co nas czeka tej jesieni. Może nie będzie to skok porównywalny do przejścia od wersji 5.6 do 7.0, jednak na kilka nowych “ficzerów” wyczekuje z radością. Będą to między innymi:

  • Możliwość deklaracji typów już na poziomie deklaracji klasy. Jest to rozwinięcie zapoczątkowanej od php 7.0 funkcjonalności pozwalającej zadeklarować typ dla typów prostych (string, integer, float …itp.). Zatem komentarze w kodzie takie jak: @param, @return będą mogły pójść w zapomnienie.
  • Skrócona składnia dla domknięć (ang. closures)
    tj. z function ($x) use ($arr) { return $arr[$x]; } do fn($x) => $arr[$x].
  • Null Coalescing Assignment Operator czyli umożliwienie wpisania wartości domyślnej dla zmiennej, gdy nie posiada ustawionej żadnej wartości (taki ukryty isset()).
  • Aktulizacja dla Spread operator o możliwość wypakowania całej tablicy w jednej instrukcji.
<?php
$vine_veges = ['cucumber', 'pumpkin'];
$ground_veges = ['carrots', 'potatos'];

print_r(['eggplant', ...$vine_veges, ...$ground_veges]);

/*
Array
(
    [0] => eggplant
    [1] => cucumber
    [2] => pumpkin
    [3] => carrots
    [4] => potatos
)
*/
z https://www.pixelite.co.nz/article/new-features-in-php-7-4/ 

Jak widać, jest na co czekać, więcej informacji
https://wiki.php.net/todo/php74
https://www.pixelite.co.nz/article/new-features-in-php-7-4/

Efektywne uczenie się programowania

Inspiracją do powstania tego dzisiejszego wpisu jest poniższy artykuł

Kiedy chodziłem do szkoły nauka nie wydawała się taka przyjemna, a przecież była konieczna. Brakowało mi w tym wszystkim nie tylko celu, motywacji, ale też i sensu. Z programowaniem było jednak inaczej. Już od czasów gdy mój pierwszy komputer z Windows 95 na pokładzie i modemem 56 Kbps (https://www.pcworld.pl/news/Demony-szybkosci-modemy-56-Kbps,297544.html) zagościł w domu, pojawiła się ciekawość, a za tym i motywacja. Właściwie od tego czasu, stale poszukuje sposobów na bardziej efektywne przyswajanie nowego materiału, czy to nowych technologi, idei, wzorców, algorytmów, rozwiązań etc. ..a przecież jak widać trochę tego jest, szczególnie w naszej branży IT. Jak zatem niepogubić się w tym wsystkim i się rozwijać ? Jak to ujął autor,

“nie uczy się ani zbyt głęboko, ani zbyt płytko”

To trudne, zwłaszcza w IT, gdzie musimy równoważyć teorę i praktykę. Wydaje się jednak dobrym pomysłem by z jednej strony poszerzać informacje o tym co dzieje się w całej branży, ale też jednocześnie specjalizować się w konkretnej dziedzinie, która nas kręci. Przytoczony “Warstwowy model nauki programowania” Przemka Smyrdka przyporządkowuje aktywności do konkretnych faz uczenia się programowania przy użyciu metafory lejka sprzedaży. Zaczynamy od “Świadomości“, tutaj przegląd nowinek w Social Media, udział w konferencjach, meetupach czy ogólnie rozmowach o programowaniu. Ta kategoria cechuje się niskim stopniem zaangażowania, za to wysoką ilością poznanych tematów. Później mamy takie kategorie jak “Zainteresowanie“, “Porównanie“, “Ewaluacja“, “Działanie” a lejek kończy “Promowanie“, czyli dzielenie się wiedzą z innymi. “Promowanie” jest kategorią z dużym stopniem zaangażowania i jest ograniczona do konkretnych tematów. Zdaniem twórcy taki model pozwoli nam usystematyzować nasz system uczenia się, a zatem pomoże podnieść efektywność 😉

Ja bym jeszcze do tego dorzucił odpuszczanie ( https://dominikjuszczyk.pl/2019/06/110-odpuszczanie/ ). Nie ma co robić sobie wyrzutów, że się wszystkiego nie wie.

Microsoft SQL i docker i…Linux ;-)

Przyznaje, że tytuł może się wydawać dosyć prowokacyjny, ale nie da się zaprzeczyć, że Microsoft coraz bardziej otwiera się na Linuxa. Wydaje się, że obecny kurs Microsoftu to odejście od traktowania go niczym “raka” (https://pl.wikiquote.org/wiki/Steve_Ballmer). Więcej na temat otwarcia się Microsoftu na środowisko open source można przeczytać na https://www.spidersweb.pl/2016/11/linux-foundation-microsoft.html. W każdym razie dzisiejszy wpis będzie o moich przygodach z skonfigurowaniem Dockera ( https://www.docker.com/ ) pod bazę Microsoft SQL Server dla jednego z moich projektów. By to zrobić użyje jeszcze docker-compose, tak aby móc wygodnie sterować wszystkimi wymaganymi kontenerami przypisanymi do projektu. Natomiast w celu utworzenia przykładowych baz danych i użytkowników trzeba będzie przebudować obraz sql servera.

Plik docker-compose.yml może wyglądać tak

version: '2.0'

services:
    mssql:
        build:
            context: .
            dockerfile: docker/Dockerfile-mssql
        image: mymssql
        networks:
            protectedNetwork:
                ipv4_address: 192.168.56.5

networks:
  protectedNetwork:
    ipam:
     config:
       - subnet: 192.168.56.0/24
         gateway: 192.168.56.99 # disable traffic

Wszystkie artefakty konieczne do budowy obrazu będę trzymał w katalogu docker/. W projekcie potrzebna jest wewnętrzna sieć (tj. protectedNetwork) do współdzielenia zasobów między kontenerami.

Plik docker/Dockerfile-mssql

FROM microsoft/mssql-server-linux

RUN mkdir -p /opt/mssql-scripts
ADD docker/mssql.sql /opt/mssql-scripts

ENV SA_PASSWORD tajneHaslo
ENV ACCEPT_EULA Y

RUN ( /opt/mssql/bin/sqlservr --accept-eula & ) | grep -q "Service Broker manager has started" \
    && /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'tajneHaslo' -i /opt/mssql-scripts/mssql.sql \
    && pkill sqlservr 

W tym miejscu instruujemy Dockera by pobrał utworzony przez Microsoft gotowy obraz SQL Servera, utworzył katalog i skopiował do niego plik z instrukcjami sql (mssql.sql, zawartość podana niżej). By obraz był gotowy należy jeszcze utworzyć zmienne środowiskowe z hasłem i akceptacją umowy (SA_PASSWORD, ACCEPT_EULA). Ostatnia linia wymusza uruchomienie Sql Servera ( w trakcie budowy obrazu), aby załadować plik sql.

Plik docker/mssql.sql

CREATE DATABASE databasemock;
GO
CREATE LOGIN databasemockLogin WITH PASSWORD = 'databaseMock1#';
USE databasemock;
CREATE USER databasemockUser FOR LOGIN databasemockLogin WITH DEFAULT_SCHEMA = databasemock;
GO
GRANT ALL TO databasemockUser;
GO

Plik z instrukcjami SQL zawiera jedynie utworzenie bazy danych i użytkownika z uprawnieniami do niej.

Po zbudowaniu obrazu (docker-compose build) i uruchomienia kontenerów (docker-compose up), w celu podłączenia się do bazy danych można użyć narzędzia sqlcmd. Również jest on dostępny dla Linuxa 😉 https://docs.microsoft.com/en-us/sql/linux/sql-server-linux-setup-tools?view=sql-server-2017.

W uproszczeniu /opt/mssql-tools/bin/sqlcmd -S Adres IP,1433 -U Użytkownik -h -1 -P ‘hasło’ -d NazwaBazayDanych

Parametr -h -1 jest opcjonalny, wyłącza wyświetlanie nagłówów tj. na ekranie będą prezentowane jedynie wyniki zapytań bez dodatkowego ich formatowania.

Polecenia SQL można wysłać z pliku (jako batch) po przez parametr -i nazwapliku, albo wykonać z terminala np. jako skrypt, parametr -Q “Zapytanie SQL”

Właściwie to tyle na dziś. Nie wiem jak Wy, ale ja jestem pod wrażeniem obecną polityką Microsoftu. Oby tak dalej!

Kiedy CQRS a kiedy CRUD ?

Dzisiaj trochę o architekturze systemów informatycznych. Wzorzec Command Query Responsibility Segregation (CQRS) a dobrze już poznany model Create Read Update and Delete (CRUD). Na początek krótka charakterystyka, a w dalszej części spróbuje opisać w jakich przypadkach Fowler uważa CQRS za lepsze rozwiązanie.

CQRS pozwala nam potraktować dane w odmienny sposób niż w tradycyjnym ujęciu CRUD. Dla przykładu składanie modelu z wielu rekordów czy tworzenie jakiegoś wirtualnego rekordu łączącego różne miejsca. W przypadku aktualizacji danych może to być określenie reguł sprawdzenia poprawności tych danych czy też zezwolenie na przechowywanie tylko pewnych ich kombinacji. Widać tutaj, że użytkownik tak naprawdę korzysta z wielu różnych od siebie reprezentacji tych informacji.

Problem w tym, że w takim utartym już podejściu CRUD, gdy istnieje potrzeba rozdzielenia i na nowo zdefiniowania nowego modelu danych to siłą rzeczy sprowadzamy ją do jednego miejsca, która działa jako punkt integracji tych wszystkich koncepcji. Natomiast CQRS rozdziela ten model na modele aktualizacji (Command) i wyświetlania (Query). Fowler jako uzasadnienie tego podziału wskazuje, że w przypadku bardziej skomplikowanych aplikacji, posiadanie wspólnego modelu (dla poleceń i zapytań), czyni kod zbyt złożonym.

Zatem wracając do pytania od którego zacząłem kiedy CQRS a kiedy CRUD ? Zdaniem Fowlera nie powinno się używać CQRS w całej aplikacji, a jedynie w określonych jej częściach (np. BoundedContext w języku DDD). Co więcej owa część (domena) musi dać się zamodelować w taki sposób, inaczej niepotrzebnie zwiększymy złożoność kodu. Zaletą zastosowania CQRS jest też poprawa wydajności aplikacji. Dzieje się tak poprzez podział obowiązków, obie części programu można skalować w sposób od siebie niezależny. Jak również wprowadzając różne strategie optymalizacji czy techniki dostępu do bazy danych.

Reasumując CQRS jest wzorcem, o którym warto sobie przypomnieć, gdy tradycyjny model CRUD zawodzi. Trzeba jednak mieć na uwadze, że jego implementacja nie należy do łatwych. Stosując go jedynie w takich częściach systemu, gdzie wspomniany podział wydaje się być naturalny.

Źródło: https://martinfowler.com/bliki/CQRS.html

set_error_handler jako elegancki sposób na bolączki z PHP

Gdy PHP powstawało nikt nawet nie śnił, że to narzędzie znajdzie zastosowanie w dużych projektach informatycznych aniżeli tylko w prostych skryptach. Pewnym tego pokłosiem jest wprowadzenie mechanizmu wyjątków (https://www.php.net/manual/en/language.exceptions.php) dopiero w wersji 5. Niestety ze względu na kompatybilność sporo wbudowanych funkcji, struktur języka takich jak np. fopen(), mail(), dzielenie przez zero etc. nie zgłasza wyjątków a E_ERROR, E_WARNING.. (https://php.net/manual/en/errorfunc.constants.php).

Jeśli zamiast tego chcielibyśmy otrzymać wyjątek lub inaczej obsłużyć taką sytuacje to trzeba customizować obsługę błędów w PHP właśnie przez set_error_handler(). Poniżej przykład zaczerpnięty z Zend Mail (zendframework/zend-mail/src/Transport/Sendmail.php)

/**
 * Send mail using PHP native mail()
 *
 * @param  string $to
 * @param  string $subject
 * @param  string $message
 * @param  string $headers
 * @param  $parameters
 * @throws \Zend\Mail\Transport\Exception\RuntimeException
 */
public function mailHandler($to, $subject, $message, $headers, $parameters)
{
    set_error_handler([$this, 'handleMailErrors']);
    if ($parameters === null) {
        $result = mail($to, $subject, $message, $headers);
    } else {
        $result = mail($to, $subject, $message, $headers, $parameters);
    }
    restore_error_handler();

    if ($this->errstr !== null || ! $result) {
        $errstr = $this->errstr;
        if (empty($errstr)) {
            $errstr = 'Unknown error';
        }
        throw new Exception\RuntimeException('Unable to send mail: ' . $errstr);
    }
}

Z tego miejsca warto przypomnieć, że gdy już się customizuje error handler to należy zadbać o zapisanie kompletnej informacji o pochodzeniu błędu (komunikat, miejsce wywołania i inne dodatkowe informacje ułatwiające debugowanie).

Więcej w https://www.php.net/manual/en/function.set-error-handler.php

Skąd biorą się wielcy programiści

Dzisiejszy wpis chciałem by był o wzorcu Remote Facade jako uzupełnienie poprzedniego wątku. Jednak nie będzie o tym bo za sprawą Pawła (tego od alpak) wpadł mi artykuł autorstwa Eduards Sizovs, link na końcu. Wyczytałem tam kilka ciekawych sugestii jak powinien wyglądać proces rekrutacji dewelopera, ale też o przeszkodach jakie mogą czyhać w rozwoju samej kariery. Skupmy się na tej drugiej kwestii oraz porozmawiajmy o tym skąd właściwie biorą się ci wielcy programiści.

Każdemu z nas zależy by być w czymś dobrym, by stać się mistrzem w swojej profesji, by okiełznać te wszystkie przeszkody rzucone po drodze i odnieść sukces. Problem w tym jak tego dokonać, kiedy bardziej jesteśmy skupieni na dostarczaniu oprogramowania a w mniejszym stopniu na własnym rozwoju. Wydaje mi się, że najbardziej istotne jest uświadomienie sobie w tym wszystkim, że

“Wielcy programiści są wychowywani a nie zatrudniani”

Wynika z tego, że każdy ma szanse. Trzeba zdekomponować problem na mniejsze składowe. Znaleźdź mentora. Zaplanować swoją karierę…hmm wydaje się to nazbyt proste… Bo co z emocjami? Co z zwątpieniem w swoje możliwości lub wypaleniem? Jak poradzić sobie gdy czujesz, że projekt Cię przerasta? Cóż…dobrze wtedy być w takim zespole, w którym tworzy się miejsca pracy do rozwoju zamiast szukać tego wielkiego programiste…

Link do oryginalnego wpisu https://sizovs.net/2019/04/10/the-best-developers-are-raised-not-hired/

Data Transfer Object – Obiekt Transferu Danych

Co raz cześciej przychodzi mi pisać aplikacje w środowisku rozproszonym to też dzisiejszy, inauguracyjny wpis będzie o Data Transfer Object – Obiekt Transferu Danych, zwany także przez niektórych jako Value Object. W skrócie

Obiekt przenoszący dane pomiędzy procesami, umożliwiający zmniejszenie ilości wywoływanych metod M. Fowler

Tylko co się za tym kryje? Jednym z wielu problemów używania obiektów rozproszonych jest ich koszt wywołania. To czas, który trzeba ponieść, by wywołać zdalną metodę. W przypadku wywołań we wspólnej przestrzeni adresowej danego procesu, nie musimy przejmować się tym czasem. Natomiast sytuacja radykalnie się zmienia, kiedy potrzebujemy wywołać metodę na innym procesorze, w szczególności gdy ów procesor znajduje się na drugim krańcu kuli ziemskiej.

Jednym ze sposóbów radzenia sobię z tym problem jest zmniejszenie ilości wywołań takich metod. Można do tego podejść po przez przesłanie większej ilości danych niż jest to konieczne za jednym razem. Nie jest to jednak takie proste w implementacji. Niektóre języki programowania jak np. Java wymuszają zwrócenie tylko jednej wartości. Dlatego tutaj z pomocą przychodzi nam wzorzec Data Transfer Object, który z pozoru wygląda jak klasa mająca jedynie swoje pola, akcesory i mutatory. Jednakże dodatkowo owa klasa wzbogacana jest o serializacje

Serializacja

To właśnie dzięki niej, DTO jest w stanie przesłać całą swoją zawartość przez sieć w jednym wywołaniu. Natomiast konkretny używany format zapisu zależy już od programów. Wiele języków programowania jak np. PHP czy Java posiadają gotowe do tego mechanizmy. Trzeba jednak pamiętać, aby zarówno klient jak i serwer używały tego samego mechanizmu. Pewnym dylematem jest czy wybrać serializacje do postaci binarnej czy tekstowej np. XML, JSON. Kuszące jest to drugie rozwiązanie ze względu na łatwość odczytu, ale wymaga to większej przepustowości. Innym problemem jest synchronizacja obiektów transferu danych pomiędzy komunikującymi się stronami. Gdy serwer zmienia dane, taka aktualizacja powinna zajść także po stronie klienta…w teorii. W praktyce po prostu dostaniemy błąd.

Jak utworzyć taki obiekt DTO ?

Zdaniem Fowlera takie obiekty nie powinny być częścią dziedziny, ale też obiekty dziedziny nie powinny być zależne od obiektów transferu danych (bo struktura tych drugich może się zmieniać wraz ze zmianami formatów przesyłanych danych). Tutaj Fowler sugeruje użycie “obiektów grupujących” / “asemblerów“, czyli obiektów tworzących DTO używajac interfejsów modelu dziedziny. Takie obiekty grupujące przypominają wzorzec Mapper, mapują obiekt dziedziny na obiekt transferu danych.

Na zakończenie, warto nadmienić, że DTO całkiem fajnie współpracuje z innym wzorcem – Remote Facade, ale o tym kiedy indziej…

Więcej informacji na ten temat w ksiażce “Architektura Systemów Zarządzania Przedsiębiorstwem. Wzorce projektowe“. M. Fowler.