четверг, 17 августа 2017 г.

Duck Hierarchy

В этой статье больше шутки нежели полезной информацией.
Под влиянием книги Head First Design Patterns я когда-то давно нарисовал такую картинку.

Слева мы видим типичное наследование и проблему двойного наследования когда хотим реализовать в одном классе два поведения.
А справа наследование от абстрактного класса и реализация интерфейсов. В этом случае нам конечно придется писать под для каждого интерфейса, зато это провоцирует нас писать классы с одной обязанностью как и ожидает от нас один из пунктов принципа SOLID.

А под конец картинка про фабрику

вторник, 8 августа 2017 г.

Мой самый сложный SQL запрос

Я работу по большей части во фронтэнде на как-то раза два у меня была задача написать сложный SQL-запрос. В принципе получилось довольно быстро при использовании интернет. Может кому и пригодиться.

Задача 1 - посчитать количество ордеров определенного типа


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
DROP TABLE IF EXISTS Objects;
DROP TABLE IF EXISTS ObjectTypes;
DROP TABLE IF EXISTS Attrs;
DROP TABLE IF EXISTS Params;

CREATE TABLE Objects (id int, objectTypeId int, name varchar(30));
INSERT INTO Objects (id, objectTypeId, name) VALUES (1, 1, 'Order-Tv#1');
INSERT INTO Objects (id, objectTypeId, name) VALUES (2, 1, 'Order-Tv#2');
INSERT INTO Objects (id, objectTypeId, name) VALUES (3, 1, 'Order-Tv#3');
INSERT INTO Objects (id, objectTypeId, name) VALUES (4, 1, 'Order-Internet#1');
INSERT INTO Objects (id, objectTypeId, name) VALUES (5, 1, 'Order-Internet#2');
INSERT INTO Objects (id, objectTypeId, name) VALUES (6, 1, 'Order-Radio#1');

CREATE TABLE ObjectTypes (id int, name varchar(30));
INSERT INTO ObjectTypes (id, name) VALUES (1, 'order');

CREATE TABLE Attrs (id int, name varchar(30));
INSERT INTO Attrs (id, name) VALUES (1, 'not started');
INSERT INTO Attrs (id, name) VALUES (2, 'failed');
INSERT INTO Attrs (id, name) VALUES (3, 'success');

CREATE TABLE Params (objectId int, attrId int, val varchar(30));
INSERT INTO Params (objectId, attrId, val) VALUES (1, 1, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (1, 2, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (1, 3, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (2, 1, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (3, 2, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (1, 3, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (4, 1, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (4, 4, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (5, 1, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (6, 1, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (6, 2, '+');
INSERT INTO Params (objectId, attrId, val) VALUES (6, 4, '+');

SELECT t0."type", t1."failed", t2."success", t3."not started" FROM
        (
                SELECT name AS "type" FROM ObjectTypes WHERE id IN (1)
        ) AS t0
FULL OUTER JOIN
        (
                SELECT COUNT(*) as "failed" FROM Params, Objects WHERE
                Params.objectId IN (SELECT id FROM Objects WHERE objectTypeId IN (1))
                AND Params.attrId = 1
                AND Params.value = 'failed'
                AND Params.objectId = Objects.id
                GROUP BY objectTypeId
        ) as t1
ON t0.objectTypeId = t1.ObjectTypeId
FULL OUTER JOIN
        (
                SELECT COUNT(*) as "success" FROM Params, Objects WHERE
                Params.objectId IN (SELECT id FROM Objects WHERE objectTypeId IN (1))
                AND Params.attrId = 2
                AND Params.objectId = Objects.id
                GROUP BY objectTypeId
        ) as t2
ON t0.objectTypeId = t1.ObjectTypeId
FULL OUTER JOIN
        (
                SELECT COUNT(*) as "not started" FROM Params, Objects WHERE
                Params.objectId IN (SELECT id FROM Objects WHERE objectTypeId IN (1))
                AND Params.attrId = 3
                AND Params.objectId = Objects.id
                GROUP BY objectTypeId
        ) as t3
ON t0.objectTypeId = t1.ObjectTypeId;

Cначала находим все типа объектов в строке 55 и называем такую таблицу t0. Затем находим все объекты у которых тип это Order, а атрибут используется номер 1 и записываем результат в t1 (строка 58-65). То же самое повторяем для всех типов и затем склеиваем через JOIN. Конечно здесь есть не оптимальности типа копипаста t1-t3, но я же не SQL девелопер.

Отмечу, что запрос переписан и может не работать, т.к. я убирал всю специфику и чуть упростил запрос - убрал табличу и уменьшил количество типов, а там запрос у меня занимал А4 шрифтом 10.

Задача 2 - пробежаться по иерархии

Здесь задача посложнее нужно пробежаться по иерархии Order и найти все Offering, причем Offering из родительского Order может быть заоверрайджен, тогда его показывать не нужно.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
(
  /*compatibility - get Offerings FROM Order which are children for Order Container*/
  SELECT
    OrderOffering.id AS objectId,
    OrderOffering.name AS value
  FROM
    Objects OrderContainerOffering,
    Objects OrderContainerOfferingFolder,
    Refs OrderContainer2Order,
    Objects OrderOfferingFolder,
    Objects OrderOffering
  WHERE
    OrderContainerOffering.id IN (#$objectids$#)
    AND OrderContainerOfferingFolder.id = OrderContainerOffering.parentId
    AND OrderContainer2Order.objectId = OrderContainerOfferingFolder.parentId
    AND OrderContainer2Order.attrId = 1 /*order*/
    AND OrderOfferingFolder.parentId = OrderContainer2Order.objectRefId
    AND OrderOfferingFolder.typeId = OrderContainerOfferingFolder.typeId
    AND OrderOffering.parentId = OrderOfferingFolder.id
    AND OrderOffering.typeId = 1 /*offering*/
)
UNION
(
  /*get Offerings FROM Order hierarchy*/
  SELECT
    ParentOrderOffering.id AS objectId,
    ParentOrderOffering.name AS value
  FROM
    Objects OrderOffering,
    Objects OrderOfferingFolder,
    Objects ParentOrderOfferingFolder,
    Objects ParentOrderOffering
  WHERE
    OrderOffering.id IN (#$objectids$#)
    AND OrderOfferingFolder.id = OrderOffering.parentId
    AND ParentOrderOfferingFolder.parentId IN (
      /*get all parent Orders*/
      SELECT reference FROM Refs ref
      START WITH ref.objectId = OrderOfferingFolder.parentId
      /*OrderOfferingFolder.parentId: parent of Offering Folder
        is Order AND this is Order to start find parents FROM*/
      CONNECT BY PRIOR ref.objectRefId = ref.objectId 
      AND PRIOR ref.attrId = 2 /*parent order*/
    )
    AND ParentOrderOffering.parentId = ParentOrderOfferingFolder.id
    AND ParentOrderOffering.typeId = 1 /*offering*/
    AND ParentOrderOffering.id not IN (
      /*exclude Offerings which are already overrided - 
        only not overrided Offerings available for override*/
      SELECT reference FROM Refs WHERE attr_id = 3 /*ancestor offering*/
      AND objectId IN (
        /*SELECT Offerings which are set IN attr ancestor offering*/
                SELECT /*get offerings FROM order hierarchy*/
          ParentOrderOffering.id AS objectId
                FROM
          Objects OrderOffering,
          Objects OrderOfferingFolder,
          Objects ParentOrderOfferingFolder,
          Objects ParentOrderOffering
                WHERE
          OrderOffering.id IN (#$objectids$#)
          AND OrderOfferingFolder.id = OrderOffering.parentId
          AND ParentOrderOfferingFolder.parentId IN (
            /*get all parent orders*/
            SELECT reference FROM Refs ref
            START WITH ref.objectId = OrderOfferingFolder.parentId
            /*OrderOfferingFolder.parentId: parent of Offering folder 
              is Order AND this is Order to start find parents FROM*/
            CONNECT BY PRIOR ref.reference = ref.object_id 
            AND PRIOR ref.attr_id = 2 /*attr parent order*/
            UNION ALL
            SELECT id FROM Objects 
            WHERE id = OrderOfferingFolder.parentId /*plus current Order*/
          )
          AND ParentOrderOffering.parentId = ParentOrderOfferingFolder.id
          AND ParentOrderOffering.typeId = 1 /*offering*/
      )
    )
)

Итог

  • я познакомился с иерархическими запросами - START WITH
  • первый раз использовал UNION
  • наконец в боевых условиях использовал JOIN
  • попрактиковался в сложных WHERE и GROUP BY
  • использовал вложенные запросы
  • сделал параметризацию запросов
  • попробовал AS для именования
SQL не такой уж и сложный поначалу, хотя конечно чем дальше тем дремучей лес.

понедельник, 7 августа 2017 г.

Периодически забываю как делать сложные enum с поиском по строке (метод valueOfPretty), поэтому вот заготовка.

public enum EnumExample {
    ZERO(0, "zero value"),
    ONE(1, "one value"),
    NULL(-1, "null value") {
        @Override
        public String toString() {
            return super.toString() + "special null value";
        }
    };

    private int value;
    private String description;

    EnumExample(int value, String description) {
        this.value = value;
        this.description = description;
    }

    public String asPretty() {
        return this.name().toLowerCase();
    }

    public static EnumExample valueOfPretty(String s) {
        for (EnumExample state : EnumExample.values()) {
            if (state.asPretty().equals(s)) {
                return state;
            }
        }
        return null;
    }
}

среда, 5 июля 2017 г.

Коротко о frontend performance.

Насмотрелся я как-то видео и начитался статей о performance в мире frontend и решил задокументировать кратко походы. Подробное описание можно найти в интернете или в литературе внизу. Итак мой список пунктов связанных с performance, которые можно использовать для улучшения\оценки производительности:

Производительность кода

  1. Оптимизируйте код (например HashMap вместо Array)
  2. Профайл для поиска узких мест
  3. WebWorker для ресурсоемких операций
  4. Server-side rendering
  5. PWA
  6. defer\async in HTML
  7. Разделять чтение дома и запись, например fast-dom библиотека
  8. СSS анимации выполняются на gpu в отличие от js
  9. requiestAnimationFrame
  10. will-transform
  11. dom layout (через transfomZ(0) или will-change: transform)
  12. изменение innerHtml или размера ведет к перерисовке всего документа\слоя
  13. dns-prefetch, preconnect, prefetch, pretender, preload

Производительность сборки

  1. CDN (сервер должен быть ближе к клиенту)
  2. HTTP Cache Headers
  3. WebP images
  4. WOFF2 fonts или стандартные системные
  5. Code-splittind 
  6. Tree-shaking
  7. Core js\css load immediatelly and advanced js\css on DomContentLoaded (extra on Load)
  8. Service Worker for HTTPS
  9. Brotli or Zopfli compression (+20%)
  10. js\css minification, lazy sourcemaps
  11. image minification с потерей качества
  12. icons in base64

Производительность мозга (mind tricks)

  1. Что бы разница была заметна нужна разница в 20% времени
  2. 100ms response
  3. 16ms for frame
  4. 1.25sec for interaction (3sec for 3G)
  5. Предсказывать поведение пользователя (например при hover на search можно начать подгружать результат)
  6. 14 kb for critical.css
  7. как можно быстрее перевести пользователя из пассивного ожидания в активное
  8. наприсовать сначала видимую часть, потом подгрузить все что ниже по скроллу или на другом state-route

Литература

пятница, 23 июня 2017 г.

NPM auto publish via gulp

В мире JS все как-то несколько криво и не отточено в отличие от Java. Очень хорошо иллюстрирует положение вещей эту картинка
Одна из проблем то что нельзя легко настроить публикацию npm через таску в gulp. Очень долго искал и еле нашел всего в одном месте поэтому публикую решение здесь чтобы распространить решение и не потерять его. Исходное решение - http://blog.entelect.co.za/automatically-publish-to-npm-using-gulp-teamcity, к сожалению исходно решение уже недоступно

Зависимости
npm i npm --save-dev
npm i fs --save-dev

Примерно такие зависимости должны быть установлены
fs@0.0.1-security or file-system@2.2.2
npm@4.1.2

Код
var npm = require('npm');
var fs = require('fs');

gulp.task('npm-publish', function (callback) {
    var uri = ""; //uri to npm, e.g. https://registry.npmjs.org/
    var username = ""; 
    var password = "";
    var email = ""; 
 var dist = ""; //path to dist folder with package.json

    if (!username) {
        var usernameError = new Error("Username is required as an argument --username exampleUsername");
        return callback(usernameError);
    }
    if (!password) {
        var passwordError = new Error("Password is required as an argument --password  examplepassword");
        return callback(passwordError);
    }
    if (!email) {
        var emailError = new Error("Email is required as an argument --email example@email.com");
        return callback(emailError);
    }
    npm.load(null, function (loadError) {
        if (loadError) {
            return callback(loadError);
        }
        var auth = {
            username: username,
            password: password,
            email: email,
            alwaysAuth: true
        };
        var addUserParams = {
            auth: auth
        };
        //adduser and login are the same command
        npm.registry.adduser(uri, addUserParams, function (addUserError, data, raw, res) {
            if (addUserError) {
                return callback(addUserError);
            }
            var metadata = require(dist+"/package.json");
            metadata = JSON.parse(JSON.stringify(metadata));
            npm.commands.pack([dist], function (packError) {
                if (packError) {
                    logout(addUserParams, data.token);
                    return callback(packError);
                }
                var fileName = metadata.name + '-' + metadata.version + '.tgz';
                fileName = fileName.replace("@","").replace("/","-");
                var bodyPath = require.resolve('./' + fileName);
                var body = fs.createReadStream(bodyPath);
                var publishParams = {
                    metadata: metadata,
                    access: 'public',
                    body: body,
                    auth: auth
                };
                npm.registry.publish(uri, publishParams, function (publishError, resp) {
                    if (publishError) {
                        deleteArchive(fileName);
                        logout(addUserParams, data.token);
                        return callback(publishError);
                    }
                    console.log("Publish succesfull: " + JSON.stringify(resp));
                    deleteArchive(fileName);
                    logout(addUserParams, data.token, callback);
                });
            })
        });
    });
    function logout(addUserParams, token, callback) {
        addUserParams.auth.token = token;
        npm.registry.logout(uri, addUserParams, function (logoutError, data, raw, res) {
            if (callback) {
                if (logoutError) {
                    return callback(logoutError);
                }
                return callback();
            }
        });
    }
    function deleteArchive(filePath) {
        gulp.src(filePath).pipe(plugins.rimraf());
    }
});