Задачка на архитектуру

MiksIr

miksir@home:~$
Допустим, есть два REST сервиса.
Делаем для них для коннектора - ConnectorA, ConnectorB. Ясно, что они от одного абстрактного идут.

Оба сервиса дают.. ну пусть пользователей. UserA, UserB.
Есть запросы на список пользователей, одного пользователя и создание пользователя.

Нам нужно синхронизировать пользователей из A в B, т.е. получить оба списка, сравнить по какому-то признаку, создать пользователей в B.

Как бы вы строили архитектуру? Специально не хочу конкретизировать вопросы, что бы кому не лень - полностью свое накидали. Я накидал, что UserA реализует ActiveRecord, и в него инжектируется ConnectorA, но есть несколько затыков, которые не знаю, как правильно решить - по-этому, может путь и не верный.
 

MiksIr

miksir@home:~$
Задача не такая уж большая, что бы было сложно описать ее общую архитектуру. А подходов тут может быть несколько. Мне интересно, как ее другие решали бы. Возможно, потом сделаю из это задачи тест на собеседовании.
 

AmdY

Пью пиво
Команда форума
Я бы не делал лишнюю за.висимость и не инджектил бы коннектор в модель, а клепал любимый толстый контроллер
PHP:
$user1Data = Connector1::get('user/1');
$user2Data = Connector2::get('user/2');
$user1 = new User($user1Data); // инджект готовых данных, а не зависимость.
$user2 = new User($user2Data);
 

Gas

может по одной?
Мне тоже не нравится идея инжектить какой-то коннектор в AR, я бы сделал сервис-класс - ServiceUserSync или что-то такое, в который бы уже инжектил всё что нужно.
Когда есть некая логика, которая оперирует с кучей разных не совсем связанных объектов, то я всё это кладу в такие сервисы.
Хоть Фаулер и считает это плохой практикой и вырождение доменных моделей в анемичные и вообще не по DDD.
Но у него проекты на десятки или сотни человеко-лет и энтерпрайз головного мозга :)
А меня масштабы несколько скромнее.
 

Вурдалак

Продвинутый новичок
PHP:
<?php

class User { ... }

interface UserRepository { ... }

class SharedUserRepository implements UserRepository {
    public function __construct(Connector $a, Connector $b) {
        // ...
    }
}

class Controller {
    public function __construct(UserRepository $userRepository) {
        // ...
    }
}
 

MiksIr

miksir@home:~$
Мне тоже не нравится идея инжектить какой-то коннектор в AR, я бы сделал сервис-класс - ServiceUserSync или что-то такое, в который бы уже инжектил всё что нужно.
Когда есть некая логика, которая оперирует с кучей разных не совсем связанных объектов, то я всё это кладу в такие сервисы.
Хоть Фаулер и считает это плохой практикой и вырождение доменных моделей в анемичные и вообще не по DDD.
Но у него проекты на десятки или сотни человеко-лет и энтерпрайз головного мозга :)
А меня масштабы несколько скромнее.
Ну, в маленькой задаче имхо наоборот можно сделать все академично, ибо не погрязнешь в оверхеде ;)
Т.е. инжектили коннектор в сервисный класс, а там уже, как AmdY предлагает, писали все на уровне коннекторов?
А как же Single responsibility и тому подобное? ;) Получается, захардкорим в одном классе URI двух сервисов. Да и объект User тогда не нужен, получается.
 

Redjik

Джедай-мастер
Сделать в приложении один тип пользователей.
Вставлять данные после прогона через interpreter или adapter.

PHP:
$user1Data = Connector1::get('user/1');
$user2Data = Connector2::get('user/2');

// Rest1UserDataObject и Rest2UserDataObject идет от одного абстрактного UserDataObject
$parsedUser1 = new Rest1UserDataObject($user1Data);
$parsedUser2 = new Rest2UserDataObject($user2Data);

$user1 = $serviceUser->createUserFromDataObject($parsedUser1);
$user2 = $serviceUser->createUserFromDataObject($parsedUser2);
Побдобным макаром брал заказы с партенров с разным API.
 

MiksIr

miksir@home:~$
PHP:
<?php

class User { ... }

interface UserRepository { ... }

class SharedUserRepository implements UserRepository {
    public function __construct(Connector $a, Connector $b) {
        // ...
    }
}

class Controller {
    public function __construct(UserRepository $userRepository) {
        // ...
    }
}
А где sync будет? В SharedUserRepository, раз туда оба коннектора? И зачем тут User класс тогда вообще.
 

Gas

может по одной?
захардкорим в одном классе URI двух сервисов
ну урлы то в коннекторах и кроме них никто про эти урлы не знает, сами же коннекторы эти урлы тоже из конфига брать должны.

и объект User тогда не нужен
ну ты упоминал AR, значит они у тебя в базе хранятся, вот и объект User сгодится.
 

MiksIr

miksir@home:~$
Сделать в приложении один тип пользователей.
Вставлять данные после прогона через interpreter или adapter.

PHP:
$user1Data = Connector1::get('user/1');
$user2Data = Connector2::get('user/2');

// Rest1UserDataObject и Rest2UserDataObject идет от одного абстрактного UserDataObject
$parsedUser1 = new Rest1UserDataObject($user1Data);
$parsedUser2 = new Rest2UserDataObject($user2Data);

$user1 = $serviceUser->createUserFromDataObject($parsedUser1);
$user2 = $serviceUser->createUserFromDataObject($parsedUser2);
Побдобным макаром брал заказы с партенров с разным API.
Так у меня задача синхронизации двух наборов юзеров, мне как раз приводить к одному юзеру не нужно.
 

Redjik

Джедай-мастер
Что ты понимаешь под синхронизацей двух наборов юзеров?

UPD. Отобой - кажется понял, надо подумать =)
 

MiksIr

miksir@home:~$
ну урлы то в коннекторах и кроме них никто про эти урлы не знает, сами же коннекторы эти урлы тоже из конфига брать должны.
Не, коннектор - это аналог уровень драйвера базы, скажем. Т.е. по сути - это обертка над curl-ом. Что бы коннекторы знали урлы - нужно будет там делать методы getUser, getUserList, createUser.
ну ты упоминал AR, значит они у тебя в базе хранятся, вот и объект User сгодится.
AR я имею ввиду когда модель (юзер) знает как себя получить, создать, сохранить. Для этого коннектор и передается в юзера - урлы зашиты в юзере, ибо он знает - по какому урлу что спросить через коннектор. Но опять же, это моя идея. А может тут датамапер удобнее будет, хз.
 
Последнее редактирование:

MiksIr

miksir@home:~$
Пока надумалось такое.
Есть коллекции (A и B) - они умеют получать список пользователей, а так же добавлять юзеров в коллекцию. Работа с rest через коннекторы как раз в этих коллекциях зашиты.
В объект "синхронизатора" передаются эти коллекции. Там они сравниваются, и объекты юзера из одной коллекции как-то (видимо просто через массив данных) добавляются во вторую коллекцию.
 

Вурдалак

Продвинутый новичок
В SharedUserRepository, раз туда оба коннектора?
В SharedUserRepository->save($user). И тут зависит от целей, мне не очень ясно зачем нужна синхронизация.

А как же Single responsibility и тому подобное?
Некто из бывших участников бы сказал: «у меня когнитивный диссонанс».

И зачем тут User класс тогда вообще.
Что значит «зачем»? Это Entity. На уровне контроллера ты работаешь с User и его репозиторием, знать откуда какой юзер пришёл ему не нужно.
 
Последнее редактирование:

hell0w0rd

Продвинутый новичок
Может я ничего не понял - но почему не создать абстрактного юзера/интерфейс под него и юзать его? То есть я так понимаю данные usera и userb немного отличаются, но в чем-то совпадают. Отличия синхронизировать не получится, а вот сходства - запросто
 

cDLEON

Онанист РНРСlub
Задача не такая уж большая, что бы было сложно описать ее общую архитектуру. А подходов тут может быть несколько. Мне интересно, как ее другие решали бы. Возможно, потом сделаю из это задачи тест на собеседовании.
Где требования у этой задачи? Что если сервис B умрёт ? Всё отвалится? Юзер будет ждать определённый таймаут ?
Данные будут лежать в базе ? Структуры баз данных A и B разные ? Ожидаемая нагрузка ? И ещё миллион таких же вопросов.
Вы не выложили ТРЕБОВАНИЙ к архитектуре. Не выложили ЗАТЫКИ. Какой помощи вы ждёте ?
 

MiksIr

miksir@home:~$
В SharedUserRepository->save($user). И тут зависит от целей, мне не очень ясно зачем нужна синхронизация.
Эм... странный вопрос. Синхронизация нужна, что бы в B оказались все юзера из A.

Некто из бывших участников бы сказал: «у меня когнитивный диссонанс».
В общем спорить не буду, AR нарушает онный принцип. Но на AR никто не циклится, это была одна из идей. Да и потом, одно дело AR, а другое дело создать супер-класс aka контроллер (хотя мы вообще не про MVC), в который будет зашита логика всей работы с двумя сервисами.


Что значит «зачем»? Это Entity. На уровне контроллера ты работаешь с User и его репозиторием, знать откуда какой юзер пришёл ему не нужно.
Ну просто вы написали пример кодом, а по коду совсем не ясно, где этот User используется. Но в принципе сейчас понимаю, что где-то внутри репозитория?
 
Сверху