Привет!
Начинаю потихоньку возвращаться с каникул и решил написать линко-мысли-в-слухо-пост.
Подвернулся недавно вот этот пост.
Если вкратце, Лукас Эдер (автор jooq-а), предлагает тянуть json сразу из базы, не заморачиваясь на маппинг в энтити, а потом в ДТО.
И этот пост прям хорошо лёг на то, что я в последние пару месяцев качнулся в сторону "облегчения" разработки. Я тут недавно в одном из своих "хорошо" спроектированных проектов добавлял одно поле и мне для этого потребовалось потрогать штук 5-6 классов, в паре-тройке пакетов. И мне это не понравилось. А с таким подходом мне надо было бы поправить ровно два места - в схеме БД и в запросе. Это та самая вторая часть SRP - одна "вещь" должна делаться в одном месте.
До этого подхода тоже, конечно, можно докопаться с разной степенью успешности:
1. Он подходит только если json сливается напрямую в ответ, без какой либо обработки в приложении.
2. Он не подходит если итоговый ответ надо собрать из нескольких источников.
3. Слой работы с БД по факту сцепляется с веб-слоем. Если в ответе надо будет поменять имя поля, то это надо будет делать в репозе.
4. Логика помещается в БД. Тут я уже не согласен. Во-первых, это вообще сейчас модно (Datomic, VoltDB, например, реализуют транзакции только через хранимки). Во-вторых, логика всё равно остаётся в "приложении" - в том же репозитории, по соседству с вызывающим кодом. Написана просто на встроенном DSL-е.
5. Зависит от технологии работы с БД и самой БД, но скорее всего прямо json из базы вернётся каким-то специфичным для базы типом. И это уже будет нарушением принципа сокрытия информации, что совсем плохо. Это можно обойти, вернув из базы или репоза строку и получив stringly typed код.
Тем не менее, мне кажется что для простого веб АПИ к БД, такой подход должен хорошо работать. Я собираюсь попробовать:)
Так же этот подход хорошо согласуется с ещё одной идеей по облегчению разработки - замена "тупых" сервисов на функциональные типы. Мне всегда резал глаз код вроде этого:
Сейчас я хочу попробовать вместо этого писать так:
Кода меньше, а в плане связности так даже лучше - контроллер вообще зависит только от платформы.
В общем буду теперь двигать эргономичный подход в сторону баланса между удобством навигации/рефакторинга/типобезопасностью и количеством кода, который надо навигировать/рефакторить/использовать.
P.S.
Как вытянуть json в Postgres
#posts@ergonomic_code #ergo_persistance@ergonomic_code #ergo_approach@ergonomic_code #databases@ergonomic_code
Начинаю потихоньку возвращаться с каникул и решил написать линко-мысли-в-слухо-пост.
Подвернулся недавно вот этот пост.
Если вкратце, Лукас Эдер (автор jooq-а), предлагает тянуть json сразу из базы, не заморачиваясь на маппинг в энтити, а потом в ДТО.
И этот пост прям хорошо лёг на то, что я в последние пару месяцев качнулся в сторону "облегчения" разработки. Я тут недавно в одном из своих "хорошо" спроектированных проектов добавлял одно поле и мне для этого потребовалось потрогать штук 5-6 классов, в паре-тройке пакетов. И мне это не понравилось. А с таким подходом мне надо было бы поправить ровно два места - в схеме БД и в запросе. Это та самая вторая часть SRP - одна "вещь" должна делаться в одном месте.
До этого подхода тоже, конечно, можно докопаться с разной степенью успешности:
1. Он подходит только если json сливается напрямую в ответ, без какой либо обработки в приложении.
2. Он не подходит если итоговый ответ надо собрать из нескольких источников.
3. Слой работы с БД по факту сцепляется с веб-слоем. Если в ответе надо будет поменять имя поля, то это надо будет делать в репозе.
4. Логика помещается в БД. Тут я уже не согласен. Во-первых, это вообще сейчас модно (Datomic, VoltDB, например, реализуют транзакции только через хранимки). Во-вторых, логика всё равно остаётся в "приложении" - в том же репозитории, по соседству с вызывающим кодом. Написана просто на встроенном DSL-е.
5. Зависит от технологии работы с БД и самой БД, но скорее всего прямо json из базы вернётся каким-то специфичным для базы типом. И это уже будет нарушением принципа сокрытия информации, что совсем плохо. Это можно обойти, вернув из базы или репоза строку и получив stringly typed код.
Тем не менее, мне кажется что для простого веб АПИ к БД, такой подход должен хорошо работать. Я собираюсь попробовать:)
Так же этот подход хорошо согласуется с ещё одной идеей по облегчению разработки - замена "тупых" сервисов на функциональные типы. Мне всегда резал глаз код вроде этого:
class UsersConf {
@Bean fun usersRepo() = UsersRepo()
@Bean fun usersService() = UsersService(usersRepo())
@Bean fun usersController() = UsersController(usersService())
}
class UsersController(val usersService: UsersService) {
fun findById(id: Long) = userService.findById(id)
}
class UsersService(val usersRepo: UsersRepo) {
fun findById(id: Long) = userRepo.findById(id)
}Сейчас я хочу попробовать вместо этого писать так:
class UsersConf {
@Bean fun usersRepo() = UsersRepo()
@Bean fun usersController() = UsersController(usersRepo()::findById)
}
class UsersController(val findUserById: (Long) -> User) {
fun findById(id: Long) = findUserById(id)
}Кода меньше, а в плане связности так даже лучше - контроллер вообще зависит только от платформы.
В общем буду теперь двигать эргономичный подход в сторону баланса между удобством навигации/рефакторинга/типобезопасностью и количеством кода, который надо навигировать/рефакторить/использовать.
P.S.
Как вытянуть json в Postgres
#posts@ergonomic_code #ergo_persistance@ergonomic_code #ergo_approach@ergonomic_code #databases@ergonomic_code
Java, SQL and jOOQ.
mapping – Java, SQL and jOOQ.
Many applications migrating to jOOQ tend to keep "the old stuff" around by continuing executing queries with JPA, JDBC, JdbcTemplate, etc. and using jOOQ only as a query builder. This article shows numerous benefits of executing jOOQ that users are missing…
Привет!
Утренние мысли.
В рамках концепции
> В общем буду теперь двигать эргономичный подход в сторону баланса между удобством навигации/рефакторинга/типобезопасностью и количеством кода, который надо навигировать/рефакторить/использовать.
решил попробовать чутка сдать позиции, и пустить spring data jdbc в слой домена, и спринг в целом в слой приложения/юз кейсов/интеракторов. На этом фоне, внезапно подумал, что надо поглядедь на код анкл Боба - может всё-таки без этого можно обойтись и всё придумано до нас.
А там - ни одной информационной системы. Есть Fitnesse, которая судя по всему с БД вообще не работает. Есть пачка всяких игр и симуляторов, всякая мелочёвка и всё.
Тогда я решил залезть в репоз 8th light (которы вроде бы связанной с анкл Бобом). Там тоже всякая мелочёвка.
Дальше я пошёл просто шукать по гитхабу Clean Architecture. Тут уже появились демо проектики с одной сущностью. Но всё ещё ни одного реального репоза с проектом хотя бы на 5-10 сущностей.
Писать посты и рисовать красивые картинки легко - сам так делаю:) Но “Talk is cheap. Show me the code.” (c) Torvalds.
Знаете хоть один живой пример проекта с чистой архитектурой?
При том, что мне концептуально нравиться идея чистой архитектуры, мне надо учить людей писать реальный код за конкурентноспособную цену, а не демо проектики или шедевры дизайна.
Пойду дальше искать своего Грааля:)
#ergo_approach@ergonomic_code #ergo_persistance@ergonomic_code #clean_architecture@ergonomic_code #posts@ergonomic_code
Утренние мысли.
В рамках концепции
> В общем буду теперь двигать эргономичный подход в сторону баланса между удобством навигации/рефакторинга/типобезопасностью и количеством кода, который надо навигировать/рефакторить/использовать.
решил попробовать чутка сдать позиции, и пустить spring data jdbc в слой домена, и спринг в целом в слой приложения/юз кейсов/интеракторов. На этом фоне, внезапно подумал, что надо поглядедь на код анкл Боба - может всё-таки без этого можно обойтись и всё придумано до нас.
А там - ни одной информационной системы. Есть Fitnesse, которая судя по всему с БД вообще не работает. Есть пачка всяких игр и симуляторов, всякая мелочёвка и всё.
Тогда я решил залезть в репоз 8th light (которы вроде бы связанной с анкл Бобом). Там тоже всякая мелочёвка.
Дальше я пошёл просто шукать по гитхабу Clean Architecture. Тут уже появились демо проектики с одной сущностью. Но всё ещё ни одного реального репоза с проектом хотя бы на 5-10 сущностей.
Писать посты и рисовать красивые картинки легко - сам так делаю:) Но “Talk is cheap. Show me the code.” (c) Torvalds.
Знаете хоть один живой пример проекта с чистой архитектурой?
При том, что мне концептуально нравиться идея чистой архитектуры, мне надо учить людей писать реальный код за конкурентноспособную цену, а не демо проектики или шедевры дизайна.
Пойду дальше искать своего Грааля:)
#ergo_approach@ergonomic_code #ergo_persistance@ergonomic_code #clean_architecture@ergonomic_code #posts@ergonomic_code
GitHub
unclebob - Overview
Uncle Bob. Author of Clean Code. unclebob has 104 repositories available. Follow their code on GitHub.
Привет!
Пока новый пост варится - радую вас линками:)
Я нашёл базу данных мечты - EdgeDb.
Что в ней крутого:
1) Она на базе Постгреса. Т.е. ядро надёжное и если с верхним слоем будут проблемы - можно будет цепануться к Постгресу напрямую
2) У неё графово-реляионна модель данных
2.1) Она поддерживает вложенные инсёрты
2.2) Она поддерживает наследование и полиморфные связи
2.3) Она поддерживает выборку иерархичных данных
2.4) Запросы на EdgeQL композируемые (Composable)
3) Судя по гиту, пилят её уже 13 лет и сейчас пилят активно как никогда - на момент написания предыдущий мёрж в мастер был 43 минуты назад, а за последние 2 дня вмёржили 20 ПРов
4) Free как и свобода и как пиво
5) Поддерживает GraphQL из коробки
6) У неё релиз версии 1.0 в феврале
Одна проблема - нет клиентов под JVM 😂
#posts@ergonomic_code #ergo_persistance@ergonomic_code #tools@ergonomic_code #databases@ergonomic_code
Пока новый пост варится - радую вас линками:)
Я нашёл базу данных мечты - EdgeDb.
Что в ней крутого:
1) Она на базе Постгреса. Т.е. ядро надёжное и если с верхним слоем будут проблемы - можно будет цепануться к Постгресу напрямую
2) У неё графово-реляионна модель данных
2.1) Она поддерживает вложенные инсёрты
2.2) Она поддерживает наследование и полиморфные связи
2.3) Она поддерживает выборку иерархичных данных
2.4) Запросы на EdgeQL композируемые (Composable)
3) Судя по гиту, пилят её уже 13 лет и сейчас пилят активно как никогда - на момент написания предыдущий мёрж в мастер был 43 минуты назад, а за последние 2 дня вмёржили 20 ПРов
4) Free как и свобода и как пиво
5) Поддерживает GraphQL из коробки
6) У неё релиз версии 1.0 в феврале
Одна проблема - нет клиентов под JVM 😂
#posts@ergonomic_code #ergo_persistance@ergonomic_code #tools@ergonomic_code #databases@ergonomic_code
Привет!
В догонку к понедельничному посту - ещё одна СУБД с похожей архитектурой.
Mnesia - Erlang-овская СУБД, которая объединяет код и данные на одной машине и требует, чтобы функции транзакций были чистыми.
Для Джавы тоже есть встроенная СУБД - MicroStream. Но у них нет транзакций из коробки, хотя они позволяют атомарно сохранять несколько объектов
Ну и кубит тоже в ту же сторону:) Там прямо сейчас есть традиционное АПИ с begin-commit-rollback, но я его выпилю, как руки дойдут.
#ergo_persistance@ergonomic_code
В догонку к понедельничному посту - ещё одна СУБД с похожей архитектурой.
Mnesia - Erlang-овская СУБД, которая объединяет код и данные на одной машине и требует, чтобы функции транзакций были чистыми.
Для Джавы тоже есть встроенная СУБД - MicroStream. Но у них нет транзакций из коробки, хотя они позволяют атомарно сохранять несколько объектов
Ну и кубит тоже в ту же сторону:) Там прямо сейчас есть традиционное АПИ с begin-commit-rollback, но я его выпилю, как руки дойдут.
#ergo_persistance@ergonomic_code
MicroStream is now EclipseStore: Databaseless Java-Native Persistence for Microservices, Serverless, Mobile & Edge
Home
EclipseStore is a breakthrough Java-native persistence layer built for cloud-native microservices and serverless systems. EclipseStore is also great for big monoliths and runs on Android mobile, edge, and embedded devices.
Привет!
У меня есть мечта. Я хочу, чтобы данные и код моих программ располагались на одной машине, лучше - в одном процессе. Когда я закончу Эргономичный подход, я вернусь к созданию технологии, которая позволит это сделать. Если кто-то другой не сделает её раньше:)
#tools@ergonomic_code #ergo_persistance@ergonomic_code #ergo_approach@ergonomic_code
У меня есть мечта. Я хочу, чтобы данные и код моих программ располагались на одной машине, лучше - в одном процессе. Когда я закончу Эргономичный подход, я вернусь к созданию технологии, которая позволит это сделать. Если кто-то другой не сделает её раньше:)
#tools@ergonomic_code #ergo_persistance@ergonomic_code #ergo_approach@ergonomic_code
🔥2
Привет!
Я молния! опечатка по фрейду
Молния! Я разочаровался в Spring Data JDBC. Вкратце, по трём причинам: отсутствие наследования, отсутствие спецификаций и отсутствие возможности автоматически подтянуть ридонли агрегат по ссылке. Потом, может, подробнее распишу.
Поэтому я снова полез гуглить идеальный орм и откопал любопытную штуку. Он пока у меня особо доверия не вызывает и на мой у него довольно многословное АПИ, но идея прикольная.
#tools@ergonomic_code @spring_data_jdbc@ergonomic_code #ergo_persistance@ergonomic_code
Молния! Я разочаровался в Spring Data JDBC. Вкратце, по трём причинам: отсутствие наследования, отсутствие спецификаций и отсутствие возможности автоматически подтянуть ридонли агрегат по ссылке. Потом, может, подробнее распишу.
Поэтому я снова полез гуглить идеальный орм и откопал любопытную штуку. Он пока у меня особо доверия не вызывает и на мой у него довольно многословное АПИ, но идея прикольная.
#tools@ergonomic_code @spring_data_jdbc@ergonomic_code #ergo_persistance@ergonomic_code
GitHub
GitHub - babyfish-ct/jimmer: The most advanced ORM of JVM, for both java & kotlin
The most advanced ORM of JVM, for both java & kotlin - babyfish-ct/jimmer
Привет!
У меня продолжается маниакальная стадия "мне нужен идеальный ОРМ" и я накопал ещё пару любопытных линок.
Либа персистентных (в смысле переживающих процесс) коллекций для Kotlin/Java. Она, кажется начинает подгнивать, но может пригодиться в некритичных задачах
Вы говорите в ОРМах много магии? А как насчёт МетаОРМа?
Наконец, на обе штуки я уже давал ссылки, но и себе и вам напомню, что существуют примеры того, что я считаю идеальной технологией персистанса: MicroStream и Erlang Mnesia
#posts@ergonomic_code #tools@ergonomic_code #ergo_persistance@ergonomic_code
У меня продолжается маниакальная стадия "мне нужен идеальный ОРМ" и я накопал ещё пару любопытных линок.
Либа персистентных (в смысле переживающих процесс) коллекций для Kotlin/Java. Она, кажется начинает подгнивать, но может пригодиться в некритичных задачах
Вы говорите в ОРМах много магии? А как насчёт МетаОРМа?
Наконец, на обе штуки я уже давал ссылки, но и себе и вам напомню, что существуют примеры того, что я считаю идеальной технологией персистанса: MicroStream и Erlang Mnesia
#posts@ergonomic_code #tools@ergonomic_code #ergo_persistance@ergonomic_code
GitHub
GitHub - jankotek/mapdb: MapDB provides concurrent Maps, Sets and Queues backed by disk storage or off-heap-memory. It is a fast…
MapDB provides concurrent Maps, Sets and Queues backed by disk storage or off-heap-memory. It is a fast and easy to use embedded Java database engine. - jankotek/mapdb
👍3🥰1
Привет!
"Хочешь насмешить бога - расскажи ему о своих планах" (с) Народ.
Когда я писал план выше - я не учёл одну маленькую деталь - на эту неделю у меня запланирован небольшой ремонт в квартире.
А ещё то, что на этой же недели у меня заболеет ребёнок 🤯
Поэтому план уже точно поедет как минимум на неделю.
Тем не менее, вопреки обстоятельствам, я осилил написать микропост с моей нотацией описания модели предметной области и алгоритмом её допила под хранение в виде неизменямых деревьев структур данных.
#immutable_domain_model@ergonomic_code #ergo_approach@ergonomic_code #ergo_persistance@ergonomic_code
"Хочешь насмешить бога - расскажи ему о своих планах" (с) Народ.
Когда я писал план выше - я не учёл одну маленькую деталь - на эту неделю у меня запланирован небольшой ремонт в квартире.
А ещё то, что на этой же недели у меня заболеет ребёнок 🤯
Поэтому план уже точно поедет как минимум на неделю.
Тем не менее, вопреки обстоятельствам, я осилил написать микропост с моей нотацией описания модели предметной области и алгоритмом её допила под хранение в виде неизменямых деревьев структур данных.
#immutable_domain_model@ergonomic_code #ergo_approach@ergonomic_code #ergo_persistance@ergonomic_code
👍5🔥3🤯2🐳1
Привет!
Я не умер. И канал не умер. И я не забил ни на канал, ни на ЭП:)
Последнее время я активно допиливал Trainer Advisor. И теперь TA содержит всю базовую функциональность.
В цифрах это:
1) 14 таблиц
2) 12 страниц UI
3) 57 эндпоинтов
4) 4378 строк продового Kotlin-кода
5) 136 интеграционных + 2 мок-теста + 2 юнит теста + 1 архитектурный тест = 141 тестов, проходящих за 17 секунд
6) 1e2e Селениум тест
Это всё написано более-менее по текущим канонам ЭП и публично доступно для изучения и ознакомления.
И это самый большой известный мне проект, демонстрирующий какой-либо подход к разработке.
А если к этому добавить ещё и тот факт, что этот проект задеплоен и один живой пользователь внёс в него 40 единиц реальных доменных данных - то уже можно начать бросаться словами в духе "Демонстрация, не имеющая аналогов в мире".:)
—
Прямо сейчас в коде ТА хорошо проиллюстрированы две части ЭП:
1) Неизменяемая модель данных
2) Подход к тестированию
Так же с помощью 370 строк кода мне удалось решить две самые большие боли Spring Data JDBC:
1) Динамические критерии выборки
2) Подгрузку ссылочных данных для вывода на фронт (без дублирования структуры доменных объектов)
И сейчас решение этих задач у меня выглядит так:
Естественно, их можно комбинировать, но пока в проекте такой потребности нет.
Но эта работа так же помогла мне проявить пару противоречий и нерешённых проблем в ЭП:
1) С ФА есть проблема в том, что в большинстве моих проектов бизнес-логики (чистого ядра) набирается от силы процентов 10, а всё остальное - линейный круд. И кажется что это противоречит тому что ФА является одним из столпов ЭП
2) Реальный мир не всегда хорошо ложится на плоскую картину use case/workflow даже 3ей версии эргономичной структуры программ.
Что с этим делать - пока не знаю
—
В общем, раньше у меня была проблема, что я писал много текста, но мало иллюстрировал его кодом. Теперь кода у меня дофига, осталось описать его текстом, чем я и займусь в ближайшее время.
#trainer_advisor@ergonomic_code #ergo_approach@ergonomic_code #ergo_persistance@ergonomic_code
Я не умер. И канал не умер. И я не забил ни на канал, ни на ЭП:)
Последнее время я активно допиливал Trainer Advisor. И теперь TA содержит всю базовую функциональность.
В цифрах это:
1) 14 таблиц
2) 12 страниц UI
3) 57 эндпоинтов
4) 4378 строк продового Kotlin-кода
5) 136 интеграционных + 2 мок-теста + 2 юнит теста + 1 архитектурный тест = 141 тестов, проходящих за 17 секунд
6) 1e2e Селениум тест
Это всё написано более-менее по текущим канонам ЭП и публично доступно для изучения и ознакомления.
И это самый большой известный мне проект, демонстрирующий какой-либо подход к разработке.
А если к этому добавить ещё и тот факт, что этот проект задеплоен и один живой пользователь внёс в него 40 единиц реальных доменных данных - то уже можно начать бросаться словами в духе "Демонстрация, не имеющая аналогов в мире".:)
—
Прямо сейчас в коде ТА хорошо проиллюстрированы две части ЭП:
1) Неизменяемая модель данных
2) Подход к тестированию
Так же с помощью 370 строк кода мне удалось решить две самые большие боли Spring Data JDBC:
1) Динамические критерии выборки
2) Подгрузку ссылочных данных для вывода на фронт (без дублирования структуры доменных объектов)
И сейчас решение этих задач у меня выглядит так:
// Выборка по динамическим параметрам:
findAll(pageRequest) {
Client::therapistId isEqual therapistId
Client::firstName isILikeIfNotNull clientSearchDto.firstName
Client::lastName isILikeIfNotNull clientSearchDto.lastName
Client::phoneNumber isILikeIfNotNull clientSearchDto.phoneNumber
}
// Выборка с подгрузкой ссылок
object Appointment.Fetch {
val summaryRefs = listOf(Appointment::clientRef, Appointment::typeRef)
val editableRefs = summaryRefs + Appointment::therapeuticTaskRef
}
val appointment = appointmentsRepo.findById(appointmentId, Appointment.Fetch.editableRefs)
Естественно, их можно комбинировать, но пока в проекте такой потребности нет.
Но эта работа так же помогла мне проявить пару противоречий и нерешённых проблем в ЭП:
1) С ФА есть проблема в том, что в большинстве моих проектов бизнес-логики (чистого ядра) набирается от силы процентов 10, а всё остальное - линейный круд. И кажется что это противоречит тому что ФА является одним из столпов ЭП
2) Реальный мир не всегда хорошо ложится на плоскую картину use case/workflow даже 3ей версии эргономичной структуры программ.
Что с этим делать - пока не знаю
—
В общем, раньше у меня была проблема, что я писал много текста, но мало иллюстрировал его кодом. Теперь кода у меня дофига, осталось описать его текстом, чем я и займусь в ближайшее время.
#trainer_advisor@ergonomic_code #ergo_approach@ergonomic_code #ergo_persistance@ergonomic_code
GitHub
GitHub - ergonomic-code/Trainer-Advisor: Информационная система йогатерапевта и демонстрационный проект Эргономичного подхода
Информационная система йогатерапевта и демонстрационный проект Эргономичного подхода - ergonomic-code/Trainer-Advisor
🔥13👍4❤2🤯1
Привет!
Ну чтош, ютуб решил всё за нас - без подтверждения загрузить видео более чем на 15 минут не даёт и подтвердить акк на мой номер телефона тоже не даёт.
Так что представляю вам свой канал на Рутубе и первый видос на нём - РЕПЕТИЦИЯ доклада "Функциональная архитектура и Spring Data JDBC. 4 года в проде, полёт отличный"
#talks@ergonomic_code #ergo_approach@ergonomic_code #functional_architecture@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
Ну чтош, ютуб решил всё за нас - без подтверждения загрузить видео более чем на 15 минут не даёт и подтвердить акк на мой номер телефона тоже не даёт.
Так что представляю вам свой канал на Рутубе и первый видос на нём - РЕПЕТИЦИЯ доклада "Функциональная архитектура и Spring Data JDBC. 4 года в проде, полёт отличный"
#talks@ergonomic_code #ergo_approach@ergonomic_code #functional_architecture@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
RUTUBE
Эргономичный код — полная коллекция видео на RUTUBE
Канал о разработе поддерживаемых бакэндов - модульных монолитов с неизменяемой моделью данных и функциональной архитектурой, написанных в стиле data-oriented programming и покрытых тестами без моков.
https://t.me/ergonomic_code
https://azhidkov.pro
https://t.me/ergonomic_code
https://azhidkov.pro
❤16👍6🤡4
Карта канала (Так же в Max)
Добро пожаловать на канал "Эргономичный код" — канал о разработке поддерживаемых кодовых баз в общем и моём подходе к этой задаче — Эргономичном подходе.
Что такое Эргономичный подход?
По большому счёту это небольшой набор принципов, взятых в основном из классической школы TDD, функциональной архитектуры и DDD, и большой набор рецептов — моделей, методик и шаблонов —, которые позволяют команде быстро создавать кодовые базы, соответствующие этим принципам и, как следствие, лёгкие в поддержке.
Подробности на сайте Эргономичного подхода
В Эргономичном подходе есть что-то уникальное?
Да. Идея представления системы как модели её эффектов в виде диаграммы эффектов и методика проектирования на базе этой модели.
"Слова дёшевы, покажи мне код!"
Trainer Advisor — некоммерческий, но реальный (~20K строк Котлин кода, 21 таблица, 2 настоящих пользователя, горки костылей, "компромиссных решений" и исторических наслоений) проект с открытым исходным кодом, разрабатываемый по Эргономичному подходу. В этот проект можно поконтрибьютить и на своём опыте прочувствовать работу с эргономичной кодовой базой.
Project Mariotte — минимальный демонстрационный пример кодовой базы, написанной по Эргономичному подходу, на примере операции бронирования номера в отеле
Есть что посмотреть или послушать?
Да, все мои публичные выступления собраны на одной странице
А почитать, кроме канала?
Да, в блоге
В блоге и канале есть подборки:
- Кейсы (в блоге, в канале #case@ergonomic_code)
- Примеры кода (в блоге)
- Эргономичный подход (в блоге, в канале #ergo_approach@ergonomic_code)
- Эргономичное тестирование (в блоге, в канале #ergo_testing@ergonomic_code)
- Функциональная архитектура (в блоге, в канале #functional_architecture@ergonomic_code)
- Эргономиный персистанс (в блоге, в канале #ergo_persistance@ergonomic_code)
- Что ещё почитать (в блоге, в канале #books@ergonomic_code, #posts@ergonomic_code, #papers@ergonomic_code)
- Что ещё посмотреть (в блоге, в канале #talks@ergonomic_code)
Какие ваши доказательства?
Исследование "Характеристики поддерживаемых кодовых баз backend-приложений"
А у меня вопрос!
Приходите в группу - там целому сообществу (более 100 крутых инженеров) можно задать любой вопрос по тематике канала - Эргономичный подход, классическая школа ТДД, ФА, ФП, в целом дизайн модели и системный дизайн
Как мне научить ИИ агента писать код по Эргономичному подходу?
Попробуйте раннюю версию моего фреймворка для ИИ-агентов.
Добро пожаловать на канал "Эргономичный код" — канал о разработке поддерживаемых кодовых баз в общем и моём подходе к этой задаче — Эргономичном подходе.
Что такое Эргономичный подход?
По большому счёту это небольшой набор принципов, взятых в основном из классической школы TDD, функциональной архитектуры и DDD, и большой набор рецептов — моделей, методик и шаблонов —, которые позволяют команде быстро создавать кодовые базы, соответствующие этим принципам и, как следствие, лёгкие в поддержке.
Подробности на сайте Эргономичного подхода
В Эргономичном подходе есть что-то уникальное?
Да. Идея представления системы как модели её эффектов в виде диаграммы эффектов и методика проектирования на базе этой модели.
"Слова дёшевы, покажи мне код!"
Trainer Advisor — некоммерческий, но реальный (~20K строк Котлин кода, 21 таблица, 2 настоящих пользователя, горки костылей, "компромиссных решений" и исторических наслоений) проект с открытым исходным кодом, разрабатываемый по Эргономичному подходу. В этот проект можно поконтрибьютить и на своём опыте прочувствовать работу с эргономичной кодовой базой.
Project Mariotte — минимальный демонстрационный пример кодовой базы, написанной по Эргономичному подходу, на примере операции бронирования номера в отеле
Есть что посмотреть или послушать?
Да, все мои публичные выступления собраны на одной странице
А почитать, кроме канала?
Да, в блоге
В блоге и канале есть подборки:
- Кейсы (в блоге, в канале #case@ergonomic_code)
- Примеры кода (в блоге)
- Эргономичный подход (в блоге, в канале #ergo_approach@ergonomic_code)
- Эргономичное тестирование (в блоге, в канале #ergo_testing@ergonomic_code)
- Функциональная архитектура (в блоге, в канале #functional_architecture@ergonomic_code)
- Эргономиный персистанс (в блоге, в канале #ergo_persistance@ergonomic_code)
- Что ещё почитать (в блоге, в канале #books@ergonomic_code, #posts@ergonomic_code, #papers@ergonomic_code)
- Что ещё посмотреть (в блоге, в канале #talks@ergonomic_code)
Какие ваши доказательства?
Исследование "Характеристики поддерживаемых кодовых баз backend-приложений"
А у меня вопрос!
Приходите в группу - там целому сообществу (более 100 крутых инженеров) можно задать любой вопрос по тематике канала - Эргономичный подход, классическая школа ТДД, ФА, ФП, в целом дизайн модели и системный дизайн
Как мне научить ИИ агента писать код по Эргономичному подходу?
Попробуйте раннюю версию моего фреймворка для ИИ-агентов.
Эргономичный подход
Редирект на индексную страницу
🔥6❤4👍3
Привет!
Собрал на вики страничку с экспресс-курсом Spring Data JDBC :)
Плюс накидал несколько заготовок паттернов:
1. SDJ
1.1 Маппинг исключений на DataAccessException (v1.0.0)
2. Реализация домена
2.1 UUIDv7 в качестве идентификаторов сущностей (v0.0.0)
3. Проектирование HTTP/JSON (REST) API
3.1 Добавляйте название сущности к параметрам идентификаторов (v1.0.0)
3.2 Используйте в маппинге эндпоинтов полные пути (v1.0.0)
#spring_data_jdbc@ergonomic_code #ergo_persistance@ergonomic_code #ergowiki@ergonomic_code
Собрал на вики страничку с экспресс-курсом Spring Data JDBC :)
Плюс накидал несколько заготовок паттернов:
1. SDJ
1.1 Маппинг исключений на DataAccessException (v1.0.0)
2. Реализация домена
2.1 UUIDv7 в качестве идентификаторов сущностей (v0.0.0)
3. Проектирование HTTP/JSON (REST) API
3.1 Добавляйте название сущности к параметрам идентификаторов (v1.0.0)
3.2 Используйте в маппинге эндпоинтов полные пути (v1.0.0)
#spring_data_jdbc@ergonomic_code #ergo_persistance@ergonomic_code #ergowiki@ergonomic_code
Эргономичный подход
Spring Data JDBC
Шаблоны проектирования и реализации агрегатов на базе Spring Data JDBC.
SDJ на одной странице По сути своей SDJ - простой как три копейки:
Он работает с агрегатами
Агрегат - это дерево с корнем
Корень агрегата - это класс с аннотацией Table
Границы агрегата…
SDJ на одной странице По сути своей SDJ - простой как три копейки:
Он работает с агрегатами
Агрегат - это дерево с корнем
Корень агрегата - это класс с аннотацией Table
Границы агрегата…
👍3🔥3❤2
Привет!
Периодически гипотезы, лежащие в основе ЭП, рушатся о жестокую реальность промышленной разработки, что приводит к кризисам ЭП, которых уже было три штуки: раз по мотивам проекта Barcoder (решение), два (решение) и три (решение - нафик ООД, ДДД и private внутри одного репоза😀) - по мотивам #project_e.
И вот по мотивам #project_r за последние 5 дней у меня случился и благополучно разрешился очередной кризис ЭП:)
В связи с чем у меня пачка новостей.
Книга
Это уже новость второй свежести, но для контекста надо проговорить - я договорился с менеджером проектов издательства Питер начать с апреля плотную работу над книгой про ЭП. Так что надеюсь, на горизонте пары лет книга увидит свет.
Отказ от ФА
Номинально самая большая новость - я решил отказаться от ФА (которая когда-то была одним из трёх столпов ЭП).
Но по сути ничего не меняется - ЭП всё так же предполагает использование неизменяемых структур данных и максимально возможное разделение логики и ио.
Это просто признание реальности, что в ЭП, который начинался как сильно функциональный, я методично отказываюсь от отличительных черт ФА/ФП:
1. уже очень давно я отказался от монад и ROP-а в пользу защитных условий
2. затем я затащил операции в императивную оболочку
3. во многом по мотивам Проекта Р я вернул в милость выброс исключений вместо возвращения Result
4. по мотивам Проекта Р, я утащил сбалансированную форму системы с архитектурного уровня системы, на локальный уровень отдельных методов
Отказ от SDJ как дефолтной технологии работы с БД
А вот по сути самое больше изменение - я решил отказаться от SDJ в качестве дефолта для работы с БД.
Это всё ёще допустимый в рамках ЭП т.к. его всё ещё легче "продать" заказчикам и коллегам, но по умолчанию я теперь предлагаю использовать какой-то легковесный DSL - jooq или Exposed, ещё не решил что именно.
Это опять же обусловлено опытом Проекта Р - там у меня львиная доля запросов чтения рукописные, потому что SDJ не в состоянии эффективно доставать данные этого проекта, а после того, как мне пришлось ещё и два кастомных метода сохранения написать - стало понятно, что я борюсь с технологией. Этому решению так же поспособствовали пост из канала одного из участников нашей группы.
И решение перейти на UUID-ы, которое так же было обусловлено опытом разработки Проекта Р, где надо сетапать огромные графы объектов в тестах и с генераций ИДов на уровне БД это очень сложно.
Новая версия описания Эргономичной архитектуры
На мой взгляд самая крутая новость - я выложил новый подход к описанию ЭА.
По сути там ничего не изменилось, изменилась только подача.
Но эта новая подача мне настолько нравится, что я решил показать самый первый черновик.
Основная фишка - я отказался от попытки быть как все и уместить архитектуру на одну картинку и ввёл идею проекций. Их три:
1. проекция структуры данных (модель данных) - тут про агрегаты и связи между ними
2. проекция состояния (объектная модель) - тут про объекты в рантайме (порты, операции, ресурсы) и связи между ними
3. проекция структуры поведения (процедурная модель) - тут про методы и связи между ними
Плюс я туда сразу накидал кучу примеров (пока на словах😕), а так же процессы построения всех этих структур и итоговой архитектуры.
Над единой картинкой я тоже ещё подумаю: кажется, судя по проектам физических изделий, где есть проекции по плоскостям плюс изометрия или проекты зданий, где есть тоже должны быть разные проекции и разные слои (с электропроводкой, например) - для ЭА тоже можно что-то такое сделать.
И возвращаясь к книге - эта статья по сути - четверть книги. Заполнить там пробелы, добавить примеры и теоретическая часть по ЭА готова. Вспоминая про бейзкамповский Hill Chart, у меня сейчас такое ощущение, что я достиг холма реализации ЭА и дальше только дело техники. Лишь бы очередной проект не зафейлил очередную гипотезу😄
#the_book@ergonomic_code #functional_architecture@ergonomic_code #ergo_arch@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
Периодически гипотезы, лежащие в основе ЭП, рушатся о жестокую реальность промышленной разработки, что приводит к кризисам ЭП, которых уже было три штуки: раз по мотивам проекта Barcoder (решение), два (решение) и три (решение - нафик ООД, ДДД и private внутри одного репоза😀) - по мотивам #project_e.
И вот по мотивам #project_r за последние 5 дней у меня случился и благополучно разрешился очередной кризис ЭП:)
В связи с чем у меня пачка новостей.
Книга
Это уже новость второй свежести, но для контекста надо проговорить - я договорился с менеджером проектов издательства Питер начать с апреля плотную работу над книгой про ЭП. Так что надеюсь, на горизонте пары лет книга увидит свет.
Отказ от ФА
Номинально самая большая новость - я решил отказаться от ФА (которая когда-то была одним из трёх столпов ЭП).
Но по сути ничего не меняется - ЭП всё так же предполагает использование неизменяемых структур данных и максимально возможное разделение логики и ио.
Это просто признание реальности, что в ЭП, который начинался как сильно функциональный, я методично отказываюсь от отличительных черт ФА/ФП:
1. уже очень давно я отказался от монад и ROP-а в пользу защитных условий
2. затем я затащил операции в императивную оболочку
3. во многом по мотивам Проекта Р я вернул в милость выброс исключений вместо возвращения Result
4. по мотивам Проекта Р, я утащил сбалансированную форму системы с архитектурного уровня системы, на локальный уровень отдельных методов
Отказ от SDJ как дефолтной технологии работы с БД
А вот по сути самое больше изменение - я решил отказаться от SDJ в качестве дефолта для работы с БД.
Это всё ёще допустимый в рамках ЭП т.к. его всё ещё легче "продать" заказчикам и коллегам, но по умолчанию я теперь предлагаю использовать какой-то легковесный DSL - jooq или Exposed, ещё не решил что именно.
Это опять же обусловлено опытом Проекта Р - там у меня львиная доля запросов чтения рукописные, потому что SDJ не в состоянии эффективно доставать данные этого проекта, а после того, как мне пришлось ещё и два кастомных метода сохранения написать - стало понятно, что я борюсь с технологией. Этому решению так же поспособствовали пост из канала одного из участников нашей группы.
И решение перейти на UUID-ы, которое так же было обусловлено опытом разработки Проекта Р, где надо сетапать огромные графы объектов в тестах и с генераций ИДов на уровне БД это очень сложно.
Новая версия описания Эргономичной архитектуры
На мой взгляд самая крутая новость - я выложил новый подход к описанию ЭА.
По сути там ничего не изменилось, изменилась только подача.
Но эта новая подача мне настолько нравится, что я решил показать самый первый черновик.
Основная фишка - я отказался от попытки быть как все и уместить архитектуру на одну картинку и ввёл идею проекций. Их три:
1. проекция структуры данных (модель данных) - тут про агрегаты и связи между ними
2. проекция состояния (объектная модель) - тут про объекты в рантайме (порты, операции, ресурсы) и связи между ними
3. проекция структуры поведения (процедурная модель) - тут про методы и связи между ними
Плюс я туда сразу накидал кучу примеров (пока на словах😕), а так же процессы построения всех этих структур и итоговой архитектуры.
Над единой картинкой я тоже ещё подумаю: кажется, судя по проектам физических изделий, где есть проекции по плоскостям плюс изометрия или проекты зданий, где есть тоже должны быть разные проекции и разные слои (с электропроводкой, например) - для ЭА тоже можно что-то такое сделать.
И возвращаясь к книге - эта статья по сути - четверть книги. Заполнить там пробелы, добавить примеры и теоретическая часть по ЭА готова. Вспоминая про бейзкамповский Hill Chart, у меня сейчас такое ощущение, что я достиг холма реализации ЭА и дальше только дело техники. Лишь бы очередной проект не зафейлил очередную гипотезу
#the_book@ergonomic_code #functional_architecture@ergonomic_code #ergo_arch@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥6👍4❤2
Привет!
Ток что впервые за 4 года пришлось запилить руками dirty checking в SDJ...
Хотел написать я.
Но уже буквально коммитаясь я в последний момент одуплил, что я поломал основную идею ДДД, SDJ и эргономичного персистанса - у меня одна сущность оказалось частью двух агрегатов.
По бизнес процессу, она сначала появляется в одном агрегате, а потом перемещается во второй агрегат того же типа, но в другую связь (условно - из черновика в основные данные). Добавил удаление ссылки из первого агрегата при перемещении - и обошлось:)
Это же действие, заодно сделало модель более надёжной - черновики меняются на месте и без доп логики, а основные данные с версионированием и приплясками. И в оригинальной модели как раз таки была возможно похачить сущность через черновик без версионирования, а сейчас такой фокус не проканает.
5-ый кризис ЭП пролетел буквально за часик:)
Хотел написать я.
А потом упёрся в то, что есть ещё одна сущность, которую я сделал частью нескольких агрегатов одного типа...
А если учесть, что эта сущность в перспективе будет контрибьютить в инварианты этого типа агрегатов - Проект Р, кажись, не у ЭП, а у ДДД трубу зашатал...
#project_r@ergonomic_code #ddd@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
Ток что впервые за 4 года пришлось запилить руками dirty checking в SDJ...
Хотел написать я.
Но уже буквально коммитаясь я в последний момент одуплил, что я поломал основную идею ДДД, SDJ и эргономичного персистанса - у меня одна сущность оказалось частью двух агрегатов.
По бизнес процессу, она сначала появляется в одном агрегате, а потом перемещается во второй агрегат того же типа, но в другую связь (условно - из черновика в основные данные). Добавил удаление ссылки из первого агрегата при перемещении - и обошлось:)
Это же действие, заодно сделало модель более надёжной - черновики меняются на месте и без доп логики, а основные данные с версионированием и приплясками. И в оригинальной модели как раз таки была возможно похачить сущность через черновик без версионирования, а сейчас такой фокус не проканает.
5-ый кризис ЭП пролетел буквально за часик:)
Хотел написать я.
А потом упёрся в то, что есть ещё одна сущность, которую я сделал частью нескольких агрегатов одного типа...
А если учесть, что эта сущность в перспективе будет контрибьютить в инварианты этого типа агрегатов - Проект Р, кажись, не у ЭП, а у ДДД трубу зашатал...
#project_r@ergonomic_code #ddd@ergonomic_code #ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code
😱4👍1
Привет!
Докинул на вики рыбу статьи с ещё одним костылём для SDJ - как быть, если хочется сослаться на некорневую сущность агрегата.
#ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code #ergowiki@ergonomic_code
Докинул на вики рыбу статьи с ещё одним костылём для SDJ - как быть, если хочется сослаться на некорневую сущность агрегата.
#ergo_persistance@ergonomic_code #spring_data_jdbc@ergonomic_code #ergowiki@ergonomic_code
Эргономичный подход
Ссылки на некорневые сущности (v0.0.0)
По философии DDD идентичностью (глобальным идентификатором) обладают только корни агрегатов, соответственно извне агрегата ссылаться можно только на его корень.
Однако время от времени возникает необходимость сослаться на некорневую сущность агрегата.
Примером…
Однако время от времени возникает необходимость сослаться на некорневую сущность агрегата.
Примером…
👍3
Привет!
Я всё ещё "гамак-дривеню" проекцию модели данных Эргономичной архитектуры и чтобы накинуть больше входных данных на обработку во сне своему большому мозолистому мозгу я начал читать Data Modeling Essentials. И там наткнулся на новую для себя концепцию в моделировании данных - предаваемость (transferability) отношения.
Отношение является передаваемым, если допустимо менять сущность на одной из сторон.
Например, отношение "Заказ такси" -> "Водитель" является передаваемым. К сожалению.
А вот "Заказ такси" -> "Заказчик" - нет.
И там прям в книге явно написано, что непередаваемость является обазятельным свойтвом связи между сущностью и её слабой/зависимой сущностью.
И в качестве примера такой связи приводят хрестоматийный пример "Заказ" -> "Позиция заказа".
Ну и т.к., имхо, ДДД Агрегаты и сущности+слабые сущности - это одни и те же яйца только в профиль, то можно брать на вооружение эту эвристику при проектировании агрегатов - включать в агрегат только то, что невозможно передать другому агрегату. Не то чтобы супер прорыв, но эврстик много не бывает, на мой взгляд.
NB: и на всякий случай - это необходимое, но недостаточное условие - ни Заказ, ни Заказчик не являются частью агрегата друг друга.
Но непередаваемые связи могут быть не только внутри агрегата, но и между ними. И это уже стало для меня откровением.
В качестве примера в книге приводят связь между страховым полисом и агентом - в их картине мира у полиса может не быть агента, но если он есть - изменить его нельзя.
Это тоже не сказать что прорывная идея, но она была за рамками моих шор. И, возможно, это поможет мне решить одну из проблем, над которыми я бьюсь.
Касательно книги - я пока ток начал её читать и скачу в ней поссылкам, а не читаю от корки до корки, но выглядит очень много обещающе - как актуальный и исчерпывающий источник информации по моделированию данных.
#ergo_arch@ergonomic_code #ergo_data_model@ergonomic_code #ergo_persistance@ergonomic_code #books@ergonomic_code #ddd@ergonomic_code
Я всё ещё "гамак-дривеню" проекцию модели данных Эргономичной архитектуры и чтобы накинуть больше входных данных на обработку во сне своему большому мозолистому мозгу я начал читать Data Modeling Essentials. И там наткнулся на новую для себя концепцию в моделировании данных - предаваемость (transferability) отношения.
Отношение является передаваемым, если допустимо менять сущность на одной из сторон.
Например, отношение "Заказ такси" -> "Водитель" является передаваемым. К сожалению.
А вот "Заказ такси" -> "Заказчик" - нет.
И там прям в книге явно написано, что непередаваемость является обазятельным свойтвом связи между сущностью и её слабой/зависимой сущностью.
И в качестве примера такой связи приводят хрестоматийный пример "Заказ" -> "Позиция заказа".
Ну и т.к., имхо, ДДД Агрегаты и сущности+слабые сущности - это одни и те же яйца только в профиль, то можно брать на вооружение эту эвристику при проектировании агрегатов - включать в агрегат только то, что невозможно передать другому агрегату. Не то чтобы супер прорыв, но эврстик много не бывает, на мой взгляд.
NB: и на всякий случай - это необходимое, но недостаточное условие - ни Заказ, ни Заказчик не являются частью агрегата друг друга.
Но непередаваемые связи могут быть не только внутри агрегата, но и между ними. И это уже стало для меня откровением.
В качестве примера в книге приводят связь между страховым полисом и агентом - в их картине мира у полиса может не быть агента, но если он есть - изменить его нельзя.
Это тоже не сказать что прорывная идея, но она была за рамками моих шор. И, возможно, это поможет мне решить одну из проблем, над которыми я бьюсь.
Касательно книги - я пока ток начал её читать и скачу в ней поссылкам, а не читаю от корки до корки, но выглядит очень много обещающе - как актуальный и исчерпывающий источник информации по моделированию данных.
#ergo_arch@ergonomic_code #ergo_data_model@ergonomic_code #ergo_persistance@ergonomic_code #books@ergonomic_code #ddd@ergonomic_code
Эргономичный подход
Эргономичная архитектура (v3.0.0)
Текущая версия статьи написана в режиме потока сознания и пока что я не проводил даже проверки правописания, не говоря уж о том, чтобы сопроводить идеи иллюстрациями и кодом.
Предыдущие черновики и диаграммы здесь.
Введение В своей книге The Problem with…
Предыдущие черновики и диаграммы здесь.
Введение В своей книге The Problem with…
❤5👍5🔥4
Привет!
Я недавно сделал Web Push уведомления в Trainer Advisor и попутно снова узнал и придумал пачку прикольных штук, о которых накатал очередной "микропост" на 30-40 минут чтения по оценке ФФ.
Ну и по традции вот вам сразу содержание, чтобы привлечь внимание мякоткой:
* Отправка пушей
* Архитектура
* Абстракция vs очевидность эффектов и разделение ввода и вывода
* И снова про уровни абстракции/стратификацию
* Пример №1: код открытия диплинка по клику
* Пример №2: фетч ресурсов с фолбэком на кэш и кастомную страницу «Сервис недоступен"
* Postgres GENERATED-колонки для ограничений целостности в JSONB
* TrainerAdvisorApis
* В Gradle-плагин GitProperties завезли поддержку git worktrees
* Отказ от Kotest Matchers в пользу shuold*-методов
#case@ergonomic_code #ergo_approach@ergonomic_code #ergo_arch@ergonomic_code #ergo_testing@ergonomic_code #trainer_advisor@ergonomic_code #ergo_persistance@ergonomic_code
Я недавно сделал Web Push уведомления в Trainer Advisor и попутно снова узнал и придумал пачку прикольных штук, о которых накатал очередной "микропост" на 30-40 минут чтения по оценке ФФ.
Ну и по традции вот вам сразу содержание, чтобы привлечь внимание мякоткой:
* Отправка пушей
* Архитектура
* Абстракция vs очевидность эффектов и разделение ввода и вывода
* И снова про уровни абстракции/стратификацию
* Пример №1: код открытия диплинка по клику
* Пример №2: фетч ресурсов с фолбэком на кэш и кастомную страницу «Сервис недоступен"
* Postgres GENERATED-колонки для ограничений целостности в JSONB
* TrainerAdvisorApis
* В Gradle-плагин GitProperties завезли поддержку git worktrees
* Отказ от Kotest Matchers в пользу shuold*-методов
#case@ergonomic_code #ergo_approach@ergonomic_code #ergo_arch@ergonomic_code #ergo_testing@ergonomic_code #trainer_advisor@ergonomic_code #ergo_persistance@ergonomic_code
Алексей Жидков
Trainer Advisor — WebPush-уведомления - Алексей Жидков
https://azhidkov.pro/
🔥4❤3👍2