Pull to refresh
8
0
Send message

Если честно мне кажется что тема не раскрыта. На шлюз тогда ложится много ответственности за взаимодействия между собой сервисов. Если шлюз начнет лажать, то лажанет все.
Шлюз можно использовать для прослойки между клиентом и сервисом. В рамках взаимодействия между сервисами лучше организовывать общение по топологии звезда. Сервис который принял любую заявку от клиента, он должен на нее и ответить и все должно работать в рамках асинхронности. По поводу авторизации/аутентификации. Клиент думаю должен пройти аутентификацию на отдельном сервисе. А на остальных сервисах по токену доступа (свой-чужой) проходит автоматическую авторизацию (Сервисы по токену сами запрашивают у сервиса аутентификации необходимую информацию ). При этом организация ролей доступа к сервисам должна храниться на сервисе аутентификации, а организация клиентских ролей у каждого сервиса свой. Один раз прошел автоматическую авторизацию, и сервис с клиентом работает как с полноценным пользователем. При этом между сервисами устанавливаются доверительные отношения и не проводят дополнительную авторизацию клиента на своих сервисах если запрос по токену доступа пришел от сервиса.

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

если эта логика не удовлетворяет правилу единственной ответственности, то так и делается. А если вы непосредственную связную логику выносите в отдельный сервис, то вы создаете излишние связи и плодите зависимости которые нужно поддерживать.

Сейчас просто плодятся не нужные споры - а нужно ли тестировать приватные методы?

А придет к вам клиент с какой нибудь госкорпорации и скажет - хочу 100% покрытие кода, плачу хорошие деньги. И вопрос само собой отпадет.

Сейчас просто устоялась практика теста только публичной части ибо код порой бесконтрольно плодится и покрытие все и вся тестами это очень затратно. Согласно методикам прошлого века к тестам намного скурпулезней относились.

Думаю вам просто нужно на финализацию посмотреть с другого ракурса. Ее как раз введи в PHP чтобы уйти от иерархии и связности и поддержать лучшие практики в SOLID . Тот же принцип open/closed раньше строился на иерархии. А сейчас на полиморфизме. И правило перекочевало на расширение абстрактных классов и интерфейсов , а не классов реализации. Если уходить от иеархий, код становится наиболее производительным,а смысловая нагрузка уменьшается.

По поводу теста приватных случаев.

Повторюсь - это на усмотрение программиста.

Есть тесты которые оценивают работоспособность API и при настройке тестовой среды будут запускаться только они.

А есть тесты которые содействуют в разработке И это частный случай. И хорошие помощники чтобы понимать что твои приватные методы работают правильно. Если вы как руководитель скажете что они не нужны в репо, то эти тесты будут храниться у прогера, а в репо тесты по феншую команды.

По поводу публичных и приватных методов.

что protected что private это зона привата.

в горизонтальном масштабировании protected теряет смысл.

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

Если зависимости правильно внедряются через конструктор, то заменить можно без всякого sucker-а, без всяких хаков и приколов :)
А ваши тесты будут полностью раскрывать использование класса, без каких-то левых приватных методов.

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

если вы говорите про динамачиское удаление свойства в объектах,то они не удаляются, а переходят в состояние undefined при этом isset($obj-->prop)===false , а property_exist()===true. при вызове такого свойства выбросит ошибку. Для статических свойств при удалении выбросит ошибку. Поэтому не переживайте за IDE, это все происходит программно, а не вносит коррективы в код.

Если вы говорили про удаление dep в рамках рефакторинга кода, то я не улавливаю вашу мысль.

Не везде хорошо организовывать слабую связность. Слабая связность полезна при взаимодействии разных компонентов между собой и возможности их замены. Если вы реализуете компонент который состоит из десятка классов , но для взаимодействия предназначен только один или два , то не имеет смысла для всех классов формировать интерфейсы чтобы через них внедрять зависимости. Это порождает загрузку излишних классов/интерфейсов. Проще абстрагировать эти классы от внешней среды. И тогда  появляется право корректировать эти классы или менять их дизайн (когда клас используется в рамках компонента и не предназначен для внешнего использования).  Если реализован мост(интерфейс) для этого класса, то такое право пропадает. Если вы начинаете внедрять зависимости через конструктор (агрегация), а не порождать в конструкторе(композиция) , то такой класс начинает зависеть от внешней среды и создает связи.  Композитный класс сам по себе заявляет - не вмешивайтесь в меня и не расширяйте меня, я выполняю строгий функционал, я не устанавливаю связи, все мои связи во мне.

Поэтому вы сталкиваетесь с рядом ограничений если хотите использовать такой класс как то по своему.  Хотя программист который так написал , заранее предостерегая от шалавливых рук в будущем отсекая все связи. То что кто то жалуется что не по феншую - значит ему не стоит туда влазить.

И финализация от части предназначена чтобы убрать  технический долг перед наследниками. Но как правило люди пишут финальные классы и интерфейсы, но забывают про трейты в целях позволить остальным горизонтальное расширение. Если для вас реализован мост ( через интерфейс), то пишите свой компонент заимствуйте трейты, если есть. Если нет моста, то не стоит даже этот финальный класс как то расширять. Значит разработчик компонента не захотел чтобы этот класс как то расширялся (наложил табу). Если решили расширить финальный класс, используйте наследование через композицию. Или как вы сказали - паттерн адаптер.

И согласитесь если вы меняете код ради тестов, то уже что то делаете не так. Согласен код должен быть тестируемым но не в разрез концепций разработки (например когда заставляете разработчиков отказываться от финальных классов. Хотя попросту надо было разобраться в теме горизонтального расширения).

Также я заметил для вас внедрение зависимостей это панацея. Повторюсь все хорошо если в меру. Отсутствие такого поведения это не означает что код плохо спроектирован. Просто кодом это поведение не предусмотрено. Возможно программист написавший код знает что то больше, чего не знаете вы. Тогда найдите другие пути тестирования и подмены зависимостей.

Вы так рассуждаете как будто ваши знанияи истинны.
Признаю, я подобрал не правильное выражение.
Я назвал Патетерн композиции. Надо было выразится яснее - Принципы Композиции или Агрегации.
Понятия Design Paterns было введено рядом авторов в 1994 году, которые описали ряд методик построения кода . А до этого прогеры были просто в недоумении что существуют паттерны (сарказм). Назовите из как угодно петтерны, шаблоны, методологии, постулаты, концепции. Сути этого не меняет. То что уже описано в концепции ООП, вы уже не найдете в тех списках популярных паттернов, потому что незачем изобретать велосипед.
И принцип композиции и агрегации зародились за долго до ООП. И также существуют в функциональном программировании. Даже в математике есть такие определения.

И в примерах я вам эти принципы в рамках ООП описал в простоя и ясном коде.

Композиция - это монолит или не разборный компонент. Любой механик вам это подтвердит.
А Агрегация - это агрегат состоящий из составных частей которые могут взаимозаменяться.
Термины даже взяты из жизни.

Но если надо я могу вам накидать ссылок , чтобы мои знания не поддавались сомнению.
https://habr.com/ru/articles/354046/

https://en.wikipedia.org/wiki/Object-oriented_programming#Composition,_inheritance,_and_delegation

https://en.wikipedia.org/wiki/Object_composition

https://en.wikipedia.org/wiki/Composition_over_inheritance
https://mohasin-dev.medium.com/object-composition-in-php-with-example-ce5855b0473b#:~:text=Object composition in PHP%2C as,combining or composing simpler objects.

В конце концов загуглите composition principle OOP и вы увидите массу ссылок с примерами. А потом говорите что у меня знания какие то не такие.

То что вы говорили про Composite pattern. - на русском этот паттерн называется как компоновщик (скорей всего чтобы не путаться в терминах).

Это какая-то маленькая программа, в которой не очень много бизнес-логики.

А это уже принцип разделения Интерфейса в SOLID
https://ru.wikipedia.org/wiki/Принцип_разделения_интерфейса

Про финализацию скину вам статью
https://habr.com/ru/articles/482154/
Про PHPUnit. Если вы хоть раз залазили под капот модуля, то узнали бы что mock -и это создание классов через eval ('class A{...}') который наследует тестируемый класс, методы беспощадно копируются в тестируемый класс, и внедряются методы тестирования. За счет этого вы и можете переопределять методы в классе. Если для вас это чистое решение, тогда не понимаю почему через функционал PHP вторгаться в частную зону класса для проведения тестов это плохо.

Ну а поводу покрытия кода тестами https://ru.wikipedia.org/wiki/Покрытие_кода.
Вы можете разделить тесты на тесты публичных методов которые отражают способы использования и поведения API , и тогда ваши коллеги могут оценить как работает модуль, и на тесты которые хардкорно оценивают поведение модуля и в случае проблем указывают что не так.

В остальном я не хочу вступать в дискус ибо это уже субъективное мнение каждого.

Композиция это не про иерархию. Есть понятия в ООП - наследование через композицию и агрегацию, вместо иеархии.
Возможно у вас другие представления об Композиции но у меня именно такие как описал ниже.

Из просторов интернета.
Наследование через Композицию

<?php
class A {
    public function helloWorld() {
        echo 'Hello, World!';
    }
}

class B {
    protected A $a;
    
    public function __construct() {
        $this->a = new A();  // создает объект другого класса
    }
    
    public function sayHello() {
        $this->a->helloWorld();  // использует объект другого класса
    }
}

$obj = new B();
$obj->sayHello();  // Hello, World!

Наследование через Агрегацию

<?php
class A {
    public function helloWorld() {
        echo 'Hello, World!';
    }
}

class B {
    protected A $a;
    
    public function __construct(A $a) {
        $this->a = $a;
    }
    
    public function sayHello() {
        $this->a->helloWorld();  // использует объект другого класса
    }
}

$objA = new A();  // создает объект другого класса

$objB = new B($objA);
$objB->sayHello();  // Hello, World!

Данные способы вы можете применять как для для наследования, так и для организации зависимостей класса.

Легко. Например "interface Runnable{ public function run();}" говорит о том что ему достаточно реализации одного публичного метода run. Если вы пишите реализацию этого интерфейса, то делать остальные методы публичными не имеет смысла.

Публичный метод это API класса. Для этого в ооп и придумали инкапсуляцию.

Классы должны взаимодействовать через интерфейсы.

Расширяя публичную часть вы уже не имеете права к ее изменению. А лишние публичные методы это уже как мусор.

Вы можете оъявить protected методы, но если `final class`, то хоть protected хоть private, не имеет значения.

Решение конечно есть моему примеру. Но ради проведение тестов вы вносите в код больше логики, ну которая по существу не нужна.

<?php
interface DepInterface{}
interface FactoryInterface {
  public function run();
}

class Dep{}
abstract class FactoryAbstract implements FactoryInterface
{
  protected \DepInterface $dep;

  public function run ()
  {
  /....
  }
} 
final class Factory extends FactoryAbstract
{
  public function __construct ()
  {
    $this->dep= new Dep();
  }
} 


Хотя при тестировании вы можете просто заменить инъекцию
`$sucker->set('dep',new EmptyDep());`

BDD это случай когда тест описывает поведение проектируемого модуля. При этом на практике можно столкнуться с частным случаем оценки этого поведения (когда поведение сокрыто от внешней среды, но ему нужно дать анализ поведения).

Инъекция зависимостей не применяется что ли? Откуда зависимости в приватных свойствах появляются-то?


Случаи разные бывают. Порой сталкиваешься с таким что не до интерфейсов и внедрения зависимостей.
Ок. Возможно вы профессионал и у вас больше опыта. Я как любитель стремлюсь покрывать больше кода. Часто я переписываю или дописываю код , который забросил больше года назад, и только благодаря покрытию кода тестами я могу знать какую логику я заложил в код.

Любое внедрение зависимостей это уже есть Агрегация.
Например есть частный случай .
К примеру у вас есть Композиция. Нужно отрефакторить так, чтобы можно было заменить зависимость для тестирования, при этом класс должен сохранять паттерн Композиция. Дополнять класс публичными методами просто так запрещено (Любое объявление публичных методов - это официальное API ).
Также финализацию класса нужно сохранить ибо класс только для использования , а не для расширения.

<?php

class Dep{}
interface FactoryInterface {
  public function run();
}

funal class Factory implements FactoryInterface
{
  private \Dep $dep;
  public function __construct ()
  {
    $this->dep= new Dep();
  }
  public function run ()
  {
  /....
  }
} 

Смотря что реализует команда - модули или приложения. Также зависит от дизайна проектирования. Покрывать все и вся тесты это затратное время. Поэтому зачастую покрывают только публичные методы. Покрытие тестами приватных методов - это частный случай программиста. Например у вас есть один публичный метод run() в рамках интерфейса, а вся логика реализована в приватных методах и они между собой как то взаимодействуют. И чтобы предсказать поведение вам приходится писать тесты для приватных методов. Но это что касается написание модулей. Для написание приложений там другая концепция проектирования тестов

Немного внесу ясность. Если исследовать современные PHP компоненты, то вы увидите что в классах в основном приватные и публичные свойства/методы. И зачастую финализированы . Программисты уходят от расширения по иерархии и предпочитают расширяться горизонтально через интерфейсы соблюдая принципы SOLID. Все что публичное - это официальное API и его нельзя изменять. Все что приватное, это основной алгоритм кода, который может меняться с каждой версией как угодно, но сохраняя поведение API.
ТО что вы предлагаете ,то для этого достаточно писать TDD тесты при которых вы можете реализовать открытое API.
Так вот когда вы примите это за идею, в юнит тестах вы столкнётесь с тем что у вас будет реализовано минимум логики в публичных методах и максимум логики в приватных методах. И чтобы сохранять ясность в поведении приватных методов которые вы пишите, вам приходится писать тесты для приватных методов. Так вы потихоньку погружаетесь в BDD тестирование при написании модулей.
Да и в конце концов это удобно. Когда вы пишите приватный метод и тут же для него пишите проверку. И благодаря такому тесту вы можете понимать что поломалось при внесении расширений кода или его изменений.

Также вы должны понимать что в `final class` не возможно провести наследования для создания фикстуры теста. А значит простым наследованием вы не отделаетесь.
Final class означает что программист не хочет расширять класс и задумываться о поддержке зависимых компонентов.

Следующее преимущество вторжения в приватную зону, это замена зависимостей.
Если зависимости объекта хранятся в приватных свойствах, то тем самым с помощью вторжения вы можете создавать "пустышки" которые симулируют поведение зависимостей. Для чего вам это надо? Например объект работает с записями БД. А вы не хотите чтобы тестовые записи попадали в бд. И вам нужно симулировать поведение базы данных в поведении класса (это я привел для примера).

НУ а для убедительности посмотрите на компонент spatie/invade - почти 2.8M скачиваний , 267 звезд, 72 зависимости. Значит есть потребность во вторжении в приватную зону.

тот момент когда в картинках понял, а в коде не знаешь как реализовать. )))

Принцип единственной ответственности это когда класс имеет единственное назначение(а не операции). Операция,- это по своей сути действия обьекта. Например экскаваторщик может иметь два операционных набора, водить экскаватор и копать экскаватором.

Принцип открытости и закрытости означает что класс открыт для расширения, но закрыт для изменения. Дополнять класс методами само по себе не запрещено, но если расширять класс так как вы показали , вы автоматически нарушаете и принцип единственной ответственности и принцип разделения интерфейсов. Да и есть спорный момент- , при внесении в класс дополнительные публичные методы , о классе приходится знать больше чем оно необходимо. Например прогер знал о 2 методах в классе, то при расширении класса, прогеру придется обновлять эти знания. По своей сути класс должен единственный раз написан для продакшена, и в последующем закрыт для изменений и прямого расширения(по крайней мере его публичные части). Единственные причины для изменение класса -⅞ это работа над ошибками, рефакторинг и поддержка совместимости, не ломая API взаимодействия.

поэтому для расширения класса в ооп доступна способность наследования, а лучше применение наследования через паттерн композиции.

Принцип постановки Лисков

Это правило нынче уже более современно звучит и с новыми терминами.

По сути правило постановки лисков объединили с паттерном Контракты.

Да и если эту концепцию поддерживает язык ООП , то о нем сильно не стоит заморачиваться.

Помимо вами сказанного, реализуются дополнительные правила при переопределении класса дочерним классом:

  • Контравариантность типов параметров метода в подтипе. Или предусловия не могут быть усилены в подтипе. Т.е. типы параметров метода дочернего класса могут заменяться родительскими типами параметров метода основного класса.

  • Ковариация типов возвращаемых значений метода в подтипе. Или постусловия не могут быть ослаблены в подклассе. Т.е. тип результата метода дочернего класса может заменяться дочерним типом результата метода основного класса.

  • Инвариант не может быть ослаблен в подтипе. Т.е. тип свойства дочернего класса может иметь дочерний тип свойства осноного класса.

  • Новые исключения не могут быть созданы методами дочернего класса, за исключением случаев, когда они являются подтипами исключений, созданных методами основного класса.

  • (исторические ограничения)неизменяемые данные базового класса не должны быть изменяемыми в подклассе. Если данные изменяемы, изменение таких данных правильней производить через методы основного класса.

    -(конец перечислений. Далее редактор с параграфами хрень исполняет.)

    По сути правило постановки лисков объединили с паттерном Контракты.

По существу если клиент сможет сам обработать и сгенерировать несуществующие id можно обойтись таким запросом
SET @prev=1;
SELECT @prev as `before_id`,`id` as `after_id`, `id`-@prev-1 as count_ids,  @prev:=`id`  FROM `test` ORDER BY `id` ASC
Добавлю еще как вариант.В целях чтобы было.
Вычисляет все id которые не назначены.

DELIMITER //
DROP FUNCTION  IF EXISTS createRowEmptyId//
# вычисляет диапазон пропущенных id между before_id и after_id и возвращает строку для под запроса (union select id as alias;)
# аргумент alias назначает псевдоним колонке
# в глобальную переменную @prev устанавливается последний установленный id  по параметру after_id
CREATE FUNCTION `createRowEmptyId`(before_id INT, after_id INT,alias VARCHAR(25))
 RETURNS TEXT
 DETERMINISTIC
BEGIN
	DECLARE answer TEXT DEFAULT '';
	DECLARE count_ids INT DEFAULT 0;
    SET count_ids=after_id-before_id-1;
	WHILE count_ids>0 DO
		SET answer=CONCAT_WS(' ',answer,'UNION SELECT ',after_id-count_ids,' as ',alias);
		SET count_ids=count_ids-1;
	END WHILE;
	set @prev=after_id;
	RETURN answer;
END//
DELIMITER ; 
SET @prev=0;
# Основной Запрос
SELECT @select:= GROUP_CONCAT(DISTINCT createRowEmptyId(@prev,`id`) SEPARATOR ' ')  FROM  `test` ORDER BY `id` ASC ; 
# Подзапрос который сформирует таблицу несуществующих id
set @select=TRIM(@select); 
set @select=CONCAT_WS(' ',TRIM(LEADING 'UNION ' FROM @select),'ORDER BY `empty_id` ASC'); 
PREPARE stmt FROM @select;
EXECUTE stmt;
DROP FUNCTION createRowEmptyId

«суть статьи: всем привет я знаю что такое this и bind»
«Суть комментария — можно я попиарюсь».
В моем случае, я столкнулся с такой проблемой, нашел причины и предоставил на обозрение.
Так как Proxy это новая фишка и еще не объезженная, народ еще не раз вернется к этой теме.
Это просто синтаксический сахар.
Например разработчику проще воспринять.
new MyProxy(target);

чем

let myHandlerProxy={....};
.......;
new Proxy (target,myHandlerProxy); 


Если вы консервативны, вас спасет и такой вариант при объявлении класса MyProxy
new Proxy(target,MyProxy.prototype);


Помимо этого конструктор подразумевает, что можно подготовить или обработать таргет под проксирование так как вам необходимо.
Далее развивайте фантазию.
Почитал доку Tracy. Там при анализе объектов и массивов, есть ограничение по глубине вложений. Я в свое время встретился с таким же ограничением в PhpConsole и отсутствием детализированных данных. Это меня и побудило написать собственную реализацию по анализу массивов и объектов. В моем случае можно анализировать толстые объекты и массивы с неограниченной вложенностью. Конечно может возникнуть ошибка memory size, но она возникнет при очень жирных объектах. Пока это единственное ограничение. По степени необходимости, по этой части будет рефакторинг.
С объектом $modx со всеми его вложениями справляется в легкую. Также петли, не повлияют на анализ. Они вычисляются и исключаются.
А рекурсивная детализация объекта, не однократно спасала. Можно посмотреть как детализирует состояние объекта. через вызов \deb::dump($obj); Возможно есть другие компоненты которые я не до оценил. Но на меня нашло вдохновение написать что то собственное.
Когда сам смотрю, тоже кровь идет. Но сначала реализуешь идею, перекладывая мысли в код, потом ее приводишь в порядок. Но пока собираюсь духом привести все в порядок.
1

Information

Rating
Does not participate
Registered
Activity