Во-первых, Kafka — кормилица. Такое выражение я придумал после того, как изучил Kafka и начал ходить по собесам. Все, вплоть до архитекторов удивлялись моим знаниям. Прям удивляются? Да! Почему? Видимо, распространенность технологии и знания о ней массы разработчиков не коррелируют. Я чувствовал, что набираю много очков при ответе на каверзные по мнению интервьюера вопросы. Осталось еще MongoDB и PostgreSQL довести до того же уровня 😈
Вообще практически на любом собесе речь рано или поздно заходит про Kafka. Ну или у меня уже появился особый навык сводить все разговоры к этой технологии 😂 Ну а раз есть чем хвастаться, то нужно это делать 🤓
Во-вторых, Kafka — это кладезь знаний. Только посмотрите на Kafka Improvement Proposals (KIP). Что такое KIP? Это идеи по изменению архитектуры/функциональности Kafka. Некоторые уже воплощены в жизнь, а некоторые только предстоит. Каждый из KIP решает определенную проблему/задачу. Очень ценно, что эти изменения происходят в реальном продукте, пользующемся огромной популярность. Кстати, очень много идей можно почерпнуть, изучив устройство хотя бы минимально отказоустойчивого кластера и то, как взаимодействуют с таким кластером клиенты.
Если начать читать все подряд пока нет решимости, то здесь, например, приведен обзор некоторый KIP`ов по улучшению производительности. Меня в свое время набор этих техник очень впечатлил.
Мой друг, Антон, постоянно спотыкается на собеседованиях на вопросах по Kafka. Буквально сегодня он получил обратную связь от Самоката (на скрине выше). Не будьте как Антон. Давайте сделаем шаг в светлое будущее, чтобы получать офферы в компании мечты. Как и обещал на сегодняшнем вебинаре разберем частые вопросы собеседований. Заходите по ссылке в 19:00 по МСК. Если будут вопросы, то пишите в личку @senior_junior_dev .
Антон, кстати, сказал, что не сможет прийти, у него танцы 😄.
Вообще практически на любом собесе речь рано или поздно заходит про Kafka. Ну или у меня уже появился особый навык сводить все разговоры к этой технологии 😂 Ну а раз есть чем хвастаться, то нужно это делать 🤓
Во-вторых, Kafka — это кладезь знаний. Только посмотрите на Kafka Improvement Proposals (KIP). Что такое KIP? Это идеи по изменению архитектуры/функциональности Kafka. Некоторые уже воплощены в жизнь, а некоторые только предстоит. Каждый из KIP решает определенную проблему/задачу. Очень ценно, что эти изменения происходят в реальном продукте, пользующемся огромной популярность. Кстати, очень много идей можно почерпнуть, изучив устройство хотя бы минимально отказоустойчивого кластера и то, как взаимодействуют с таким кластером клиенты.
Если начать читать все подряд пока нет решимости, то здесь, например, приведен обзор некоторый KIP`ов по улучшению производительности. Меня в свое время набор этих техник очень впечатлил.
Мой друг, Антон, постоянно спотыкается на собеседованиях на вопросах по Kafka. Буквально сегодня он получил обратную связь от Самоката (на скрине выше). Не будьте как Антон. Давайте сделаем шаг в светлое будущее, чтобы получать офферы в компании мечты. Как и обещал на сегодняшнем вебинаре разберем частые вопросы собеседований. Заходите по ссылке в 19:00 по МСК. Если будут вопросы, то пишите в личку @senior_junior_dev .
Антон, кстати, сказал, что не сможет прийти, у него танцы 😄.
Хабр
Почему Kafka такая быстрая
За последние несколько лет в сфере архитектуры ПО произошли огромные изменения. Идея единственного монолитного приложения или даже нескольких крупных сервисов, разделяющих общий массив данных,...
👍3🔥2
На днях со мной созвонился коллега, js-разработчик (React), по вопросу профессионального развития. Его план выглядел следующим образом: изучить Java, чтобы стать fullstack-разработчиком и начать делать задачи в рамках текущей бизнесовой команды. Я постарался отговорить его от этой затеи, хотя в ней есть здравое зерно. Звучит противоречиво? Сейчас расскажу, почему так думаю.
В коммерческих проектах роль fullstack-разработчика я не занимал, но успел в течение полугода разрабатывать фронт для портала недвижимости на Angular в команде из 30 человек. Само по себе такое приключение было для меня, безусловно, полезно. Я познакомился с новыми для себя языком, процессами и инструментами, однако, твердо для себя решил, что fullstack не мое и стал глубже погружаться в дебри бэкенда.
Не то, что бы фронт-разработка мне была не интересна, просто я ощущал, что буду распылять свои усилия на две достаточно большие области. Кажется, что fullstack-разработчик выгоден только бизнесу, а сам такой разработчик не получает от своей универсальности больших бенефитов (только если не хочется сделать свой коммерческий проект от и до).
Сами посудите, сплошная оптимизация:
1) не нужно нанимать двух людей, когда один со всем справится, а судя по hh золотые горы таким универсалам не сулят -> экономия на зарплате
2) fullstack-разработчик испытывает меньше переключений контекста, так как выполняет более объемные и комплексные задачи -> может сделать больший объем полезной работы
3) постановка задач значительно упрощается, так как fullstack-разработчик знает процессы, контракты, ограничения (и т.д.) и на бэке и фронте -> экономим ресурс аналитика
4) отпадает необходимость синхронизации бэка с фронтом, да и ошибки на стыке технологий решаются быстрее -> убиваем сразу двух зайцев: и ресурсы экономим и ответственность за ошибки выше (на дискоммуникацию все свалить не выйдет)
Потратить время на изучение Java для дальнейшего решения бизнесовых задач — вполне понятная дорога, которая вряд ли выведет вашу карьеру на принципиально новый уровень. Java или любой другой язык бэкенда изучить все же нужно, но не для fullstack-разработки для бизнеса, а для расширения кругозора, познания новых техник и практик. Примерно так, как я когда-то сам изучил js и Angular. Какой сделать следующий шаг? Конечно, погружаться глубже в уже известные языки и инструменты. А дальше?
Дальше предлагаю пойти не по пути мейнстрима, а углубиться в Computer Science, чтобы сформировать крутой фундамент и начать делать реально крутые вещи в индустрии. Кажется в обилии курсов и книг по конкретным технологиям, эта область ушла куда-то на третий план. Вот отличный (хотя и не полный) список книг преимущественно по Computer Science. Последние несколько месяцев как раз изучаю избранные главы книги Object Oriented Software Construction Бертрана Мейера.
Наверное, еще пару лет назад я бы не дал такой совет, но сытые деньки могут скоро закончиться: LLM с каждой неделей показывают все большие успехи. По моему мнению в зоне максимального риска находятся как наименее обученный персонал (junior-разработчики), так и поверхностные разработчики-универсалы. Все же знают, что ChatGPT решает джуниорские задачи на ура? Поэтому предлагаю качать базу, копать в глубину, а не поверхностно вникать в очередную популярную технологию особенно в угоду бизнесу.
В коммерческих проектах роль fullstack-разработчика я не занимал, но успел в течение полугода разрабатывать фронт для портала недвижимости на Angular в команде из 30 человек. Само по себе такое приключение было для меня, безусловно, полезно. Я познакомился с новыми для себя языком, процессами и инструментами, однако, твердо для себя решил, что fullstack не мое и стал глубже погружаться в дебри бэкенда.
Не то, что бы фронт-разработка мне была не интересна, просто я ощущал, что буду распылять свои усилия на две достаточно большие области. Кажется, что fullstack-разработчик выгоден только бизнесу, а сам такой разработчик не получает от своей универсальности больших бенефитов (только если не хочется сделать свой коммерческий проект от и до).
Сами посудите, сплошная оптимизация:
1) не нужно нанимать двух людей, когда один со всем справится, а судя по hh золотые горы таким универсалам не сулят -> экономия на зарплате
2) fullstack-разработчик испытывает меньше переключений контекста, так как выполняет более объемные и комплексные задачи -> может сделать больший объем полезной работы
3) постановка задач значительно упрощается, так как fullstack-разработчик знает процессы, контракты, ограничения (и т.д.) и на бэке и фронте -> экономим ресурс аналитика
4) отпадает необходимость синхронизации бэка с фронтом, да и ошибки на стыке технологий решаются быстрее -> убиваем сразу двух зайцев: и ресурсы экономим и ответственность за ошибки выше (на дискоммуникацию все свалить не выйдет)
Потратить время на изучение Java для дальнейшего решения бизнесовых задач — вполне понятная дорога, которая вряд ли выведет вашу карьеру на принципиально новый уровень. Java или любой другой язык бэкенда изучить все же нужно, но не для fullstack-разработки для бизнеса, а для расширения кругозора, познания новых техник и практик. Примерно так, как я когда-то сам изучил js и Angular. Какой сделать следующий шаг? Конечно, погружаться глубже в уже известные языки и инструменты. А дальше?
Дальше предлагаю пойти не по пути мейнстрима, а углубиться в Computer Science, чтобы сформировать крутой фундамент и начать делать реально крутые вещи в индустрии. Кажется в обилии курсов и книг по конкретным технологиям, эта область ушла куда-то на третий план. Вот отличный (хотя и не полный) список книг преимущественно по Computer Science. Последние несколько месяцев как раз изучаю избранные главы книги Object Oriented Software Construction Бертрана Мейера.
Наверное, еще пару лет назад я бы не дал такой совет, но сытые деньки могут скоро закончиться: LLM с каждой неделей показывают все большие успехи. По моему мнению в зоне максимального риска находятся как наименее обученный персонал (junior-разработчики), так и поверхностные разработчики-универсалы. Все же знают, что ChatGPT решает джуниорские задачи на ура? Поэтому предлагаю качать базу, копать в глубину, а не поверхностно вникать в очередную популярную технологию особенно в угоду бизнесу.
👍12❤1❤🔥1
В качестве развлекательного контента на ночь посмотрел обзорный курс по NoSQL, который является в некотором роде реинкарнацией книги Семь баз данных за семь недель. Кажется, что только ленивый не оставил язвительный комментарий, имея в виду несколько провокационный, а значит рабочий, заголовок. Кстати, вышло второе издание книги, которое с натяжкой можно считать актуальной. Мне по душе такой формат, когда не хочется тратить много времени на изучение не нужной в момент темы, но хочется быстро расширить кругозор. Но сейчас не об этом.
Когда речь заходит о NoSQL, то чаще всего вспоминают о CAP-теореме, так как она описывает, какими свойствами обладают распределенные системы. Мы можем быстро окрестить ту или иную технологию CA, AP, CP, тем самым внести ясность для всех участников дискуссии, например, при выборе очередной БД для проекта 🧐 На самом деле сфера применения CAP-теоремы гораздо уже, чем это может показаться на первый взгляд. Предлагаю разобраться, в чем собственно суть теоремы, что значит каждая из букв в этой аббревиатуре, когда её можно применять, а когда — нет.
Несколько удивительно, что трактовку CAP-теоремы помнят абсолютно все, а вот значения понятий, которыми она оперирует, достаточно сильно искажают (хотя в английской статье на Википедии трактовка дана вполне корректная. А ведь достаточно просто обратиться к первоисточнику, что мы с вами и сделаем.
В соответствии с документом в контексте распределенных систем рассматриваются три свойства:
* Consistency — консистентность данных в системе;
* Availability — доступность узлов системы;
* Partition tolerance — способность системы переживать отказы сети (забегая вперед, скажу, что это достаточно вольная, но удобная и в целом правильная трактовка).
Теорема постулирует, что при дизайне распределенной системы вы не сможете создать такую систему, которая будет одновременно удовлетворять всем трем этим свойствам: быть и согласованной, и доступной, и при этом устойчивой к разрывам сети. Вы можете реализовать системы, которые будут удовлетворять не более чем двум любым из этих трех свойств. Здесь вроде бы все очевидно и понятно. Теперь мы можем синхронизироваться с автором статьи в понимании трех упомянутых выше свойств.
Итак, под консистентностью (Consistency) понимается свойство линеаризуемости (атомарности). Это (практически) самая сильная гарантия согласованности, которая, если говорить простыми словами, заключается в следующем: любое чтение должно увидеть последнюю зафиксированную в системе запись. Это значит, например, что любая база данных с
* асинхронной репликацией, если возможно чтения данных с фолловеров
* синхронной репликацией, если возможно чтение с фолловеров без кворума
не линеаризуема, т.е. она по определению не удовлетворяет свойству C. Огромное количество систем, подпадающих под описание выше, которые все же дают такой достаточно сильный уровень согласованности, как sequential consistency (при котором пользователь все же может прочитать устаревшие, но актуальные в прошлом данные) не являются консистентными. Вполне возможно, что в вашем случае подойдут еще более слабые гарантии согласованности.
Под доступностью (Availability) имеется в виду такое свойство, что если у вас есть кластер из нескольких распределенных, но соединенных сетью узлов, то любой узел в этой системе, если он находится в рабочем состоянии, должен иметь возможность ответить на любой ваш запрос (на чтение или запись) за конечный промежуток времени.
Звучит как очень сильная гарантия, но на самом деле, в большинстве распределенных систем этой гарантии будет недостаточно. В большинстве распределенных систем под доступностью будет, конечно же, пониматься соблюдение SLA на время, в течение которого пользователь получит ответ. Очевидно, что если ваша система/узел ответит на запрос за десять минут, то любой из пользователей сочтет вашу систему недоступной, хотя формально она будет удовлетворять требованиям CAP-теоремы.
Когда речь заходит о NoSQL, то чаще всего вспоминают о CAP-теореме, так как она описывает, какими свойствами обладают распределенные системы. Мы можем быстро окрестить ту или иную технологию CA, AP, CP, тем самым внести ясность для всех участников дискуссии, например, при выборе очередной БД для проекта 🧐 На самом деле сфера применения CAP-теоремы гораздо уже, чем это может показаться на первый взгляд. Предлагаю разобраться, в чем собственно суть теоремы, что значит каждая из букв в этой аббревиатуре, когда её можно применять, а когда — нет.
Несколько удивительно, что трактовку CAP-теоремы помнят абсолютно все, а вот значения понятий, которыми она оперирует, достаточно сильно искажают (хотя в английской статье на Википедии трактовка дана вполне корректная. А ведь достаточно просто обратиться к первоисточнику, что мы с вами и сделаем.
В соответствии с документом в контексте распределенных систем рассматриваются три свойства:
* Consistency — консистентность данных в системе;
* Availability — доступность узлов системы;
* Partition tolerance — способность системы переживать отказы сети (забегая вперед, скажу, что это достаточно вольная, но удобная и в целом правильная трактовка).
Теорема постулирует, что при дизайне распределенной системы вы не сможете создать такую систему, которая будет одновременно удовлетворять всем трем этим свойствам: быть и согласованной, и доступной, и при этом устойчивой к разрывам сети. Вы можете реализовать системы, которые будут удовлетворять не более чем двум любым из этих трех свойств. Здесь вроде бы все очевидно и понятно. Теперь мы можем синхронизироваться с автором статьи в понимании трех упомянутых выше свойств.
Итак, под консистентностью (Consistency) понимается свойство линеаризуемости (атомарности). Это (практически) самая сильная гарантия согласованности, которая, если говорить простыми словами, заключается в следующем: любое чтение должно увидеть последнюю зафиксированную в системе запись. Это значит, например, что любая база данных с
* асинхронной репликацией, если возможно чтения данных с фолловеров
* синхронной репликацией, если возможно чтение с фолловеров без кворума
не линеаризуема, т.е. она по определению не удовлетворяет свойству C. Огромное количество систем, подпадающих под описание выше, которые все же дают такой достаточно сильный уровень согласованности, как sequential consistency (при котором пользователь все же может прочитать устаревшие, но актуальные в прошлом данные) не являются консистентными. Вполне возможно, что в вашем случае подойдут еще более слабые гарантии согласованности.
Под доступностью (Availability) имеется в виду такое свойство, что если у вас есть кластер из нескольких распределенных, но соединенных сетью узлов, то любой узел в этой системе, если он находится в рабочем состоянии, должен иметь возможность ответить на любой ваш запрос (на чтение или запись) за конечный промежуток времени.
Звучит как очень сильная гарантия, но на самом деле, в большинстве распределенных систем этой гарантии будет недостаточно. В большинстве распределенных систем под доступностью будет, конечно же, пониматься соблюдение SLA на время, в течение которого пользователь получит ответ. Очевидно, что если ваша система/узел ответит на запрос за десять минут, то любой из пользователей сочтет вашу систему недоступной, хотя формально она будет удовлетворять требованиям CAP-теоремы.
🤔4
Вполне возможно, что вам надо дать гарантию доступности гораздо сильнее, чем в CAP-теореме. Например, у вас есть два дата-центра, и между ними произошел разрыв сети. Каждый из дата-центров, как и ноды в каждом из них — живы. По CAP-теореме для доступности вы обязаны давать возможность пользоваться всеми этими нодами. Но если же ваша система при определении такой ситуации начинает перенаправлять все запросы пользователя в один единственный дата-центр, то на самом деле ваша доступность как системы может быть вообще стопроцентной, то есть она может быть абсолютно доступной, но с точки зрения CAP-теоремы она доступной являться не будет, потому что часть нод, которые живы, тем не менее, не могут обслужить пользовательские запросы.
Перейдем к последнему свойству — это Partition tolerance. Исходно это возможность возникновения разрывов в сети, при которых сообщения между узлами системы могут теряться. Часто свойство перефразируют до “способность системы переживать отказы сети”, что в целом не сильно влияет на ход рассуждений. Что понимается под отказом сети? Это случай, когда вы не получаете никаких сообщений от узла А в узел Б и от узла Б в узел А, т.е. все сетевые пакеты потеряны. Вообще говоря, любая распределенная система должна переживать такие ситуации, даже несмотря на то, что все сообщения теряются между этими узлами.
Сразу же хочется вспомнить о CA-системах. Они консистентны и доступны, но тогда они не распределенные, потому что в любой распределенной сети между узлами есть сеть, а сеть в любой момент может отказать (здесь как раз лучше работает исходное определение автора). К этой группе можно отнести РСУБД, развернутые на одном узле.
Вы должны помнить, что в контексте CAP-теоремы, CP-система, например, не должна допускать чтения устаревших данных. Если вы говорите, что ваша система является AP, то вы на самом деле даете не такую уж сильную гарантию, как возможно хотите дать, потому что в распределенных системах вам часто нужна гораздо более сильная гарантия доступности, учитывающая в том числе SLA на время ответа системы.
Как вы уже, наверное, поняли, обращаясь к CAP-теореме, вы должны быть очень аккуратны. Теорема очерчивает строгие границы применимости. Еще раз, CAP-теорема — хороший инструмент, если вы им правильно пользуетесь. В остальном вы должны просто рассуждать о компромиссе между консистентностью и доступностью вашей системы (но, вероятно, уже не в терминах CAP-теоремы).
Если вам хочется почитать рефлексию на тему, то рекомендую обратиться к статье классика, Мартина Клепмана (автор книги с кабанчиком), где он подробно прошелся по каждой из характеристик.
Перейдем к последнему свойству — это Partition tolerance. Исходно это возможность возникновения разрывов в сети, при которых сообщения между узлами системы могут теряться. Часто свойство перефразируют до “способность системы переживать отказы сети”, что в целом не сильно влияет на ход рассуждений. Что понимается под отказом сети? Это случай, когда вы не получаете никаких сообщений от узла А в узел Б и от узла Б в узел А, т.е. все сетевые пакеты потеряны. Вообще говоря, любая распределенная система должна переживать такие ситуации, даже несмотря на то, что все сообщения теряются между этими узлами.
Сразу же хочется вспомнить о CA-системах. Они консистентны и доступны, но тогда они не распределенные, потому что в любой распределенной сети между узлами есть сеть, а сеть в любой момент может отказать (здесь как раз лучше работает исходное определение автора). К этой группе можно отнести РСУБД, развернутые на одном узле.
Вы должны помнить, что в контексте CAP-теоремы, CP-система, например, не должна допускать чтения устаревших данных. Если вы говорите, что ваша система является AP, то вы на самом деле даете не такую уж сильную гарантию, как возможно хотите дать, потому что в распределенных системах вам часто нужна гораздо более сильная гарантия доступности, учитывающая в том числе SLA на время ответа системы.
Как вы уже, наверное, поняли, обращаясь к CAP-теореме, вы должны быть очень аккуратны. Теорема очерчивает строгие границы применимости. Еще раз, CAP-теорема — хороший инструмент, если вы им правильно пользуетесь. В остальном вы должны просто рассуждать о компромиссе между консистентностью и доступностью вашей системы (но, вероятно, уже не в терминах CAP-теоремы).
Если вам хочется почитать рефлексию на тему, то рекомендую обратиться к статье классика, Мартина Клепмана (автор книги с кабанчиком), где он подробно прошелся по каждой из характеристик.
👍4
To cache or not to cache, in Redis lies the power
На днях выбирали технологии, которые будем завозить в новый проект. Размещать инфраструктуру планируем в Yandex.Cloud. Нагрузка на систему по моим скромным подсчетам вряд ли когда-то вырастет даже до 100 rps. В связи с этим возник вопрос: нужно ли нам рассмотреть возможность затащить Redis в проект?
Когда мы слышим о Redis, то сразу вспоминаем о кэше. Даже акроним Remote Dictionary Server намекает нам о простоте технологии. Однако стоит помнить, что Redis — это нечто большее, чем просто кэш. Давайте рассмотрим другие сферы его применения.
Первое и самое простое применение — key-value хранилище. В качестве value могут быть как достаточно простые объекты, такие как сессии пользователя, так и витиеватые документы, которые мы храним обычно в MongoDB (если вам нужны нативная поддержка вторичных индексов, гибкий язык запросов и гарантия сохранности данных). Вообще Redis часто используется в сочетании с MongoDB, которую выбирают основным решением для хранения данных.
Не в последнюю очередь Redis любят за то, что он предоставляет возможность работать с разными внутренними структурами данных, такими как:
- связные списки (Lists), с помощью которых можно организовать блокирующую очередь, например, для коммуницирующих между собой процессов (паттерн consumer-producer), или запоминать последние N элементов
- множества (Sets) с рядом соответствующих удобных команд
- отсортированные множества (Sorted sets), которые позволяют реализовать алгоритм sliding-window для ограничения трафика или банальный топ чего-либо
- потоки (Streams) с (шок) consumer-группами, которые можно использовать для фиксации и последующей обработки потока событий
- битовые карты (Bitmaps), которые можно использовать для реализации фильтра Блума или экономного отслеживания пользователей онлайн
- битовые поля (Bitfields), которые являются более гибкой альтернативой простым битовым картам
- геоиндексы (Geospatial), которые удобно использовать для работы с координатами
Особенно привлекательным выглядит наличие атомарных операций в некоторых структурах.
Сами по себе эти встроенные структуры данных Redis уже предоставляют широкий спектр возможностей, однако дополнительные библиотеки, используя эти мощные строительные блоки, выводят разработку на новый уровень продуктивности. Только посмотрите, как много возможностей предоставляет, например, библиотека radisson, написанная на Java. Мы в свой проект затащили распределенную (кластерную) блокировку RedLock, которую используем, например, для fairless доступа к распределенному ресурсу, который не может из-за нехватки ресурсов обрабатывать более одного запроса единовременно.
Подведем итоги? Говорить о Redis только в контексте кэширования невозможно. Это in-memory хранилище благодаря своей гибкости можно удобно применить к решению самых разных бизнес-задач. Мы точно затащим Redis к себе в проект и, надеюсь, сможем применить его не только как банальный кэш.
На днях выбирали технологии, которые будем завозить в новый проект. Размещать инфраструктуру планируем в Yandex.Cloud. Нагрузка на систему по моим скромным подсчетам вряд ли когда-то вырастет даже до 100 rps. В связи с этим возник вопрос: нужно ли нам рассмотреть возможность затащить Redis в проект?
Когда мы слышим о Redis, то сразу вспоминаем о кэше. Даже акроним Remote Dictionary Server намекает нам о простоте технологии. Однако стоит помнить, что Redis — это нечто большее, чем просто кэш. Давайте рассмотрим другие сферы его применения.
Первое и самое простое применение — key-value хранилище. В качестве value могут быть как достаточно простые объекты, такие как сессии пользователя, так и витиеватые документы, которые мы храним обычно в MongoDB (если вам нужны нативная поддержка вторичных индексов, гибкий язык запросов и гарантия сохранности данных). Вообще Redis часто используется в сочетании с MongoDB, которую выбирают основным решением для хранения данных.
Не в последнюю очередь Redis любят за то, что он предоставляет возможность работать с разными внутренними структурами данных, такими как:
- связные списки (Lists), с помощью которых можно организовать блокирующую очередь, например, для коммуницирующих между собой процессов (паттерн consumer-producer), или запоминать последние N элементов
- множества (Sets) с рядом соответствующих удобных команд
- отсортированные множества (Sorted sets), которые позволяют реализовать алгоритм sliding-window для ограничения трафика или банальный топ чего-либо
- потоки (Streams) с (шок) consumer-группами, которые можно использовать для фиксации и последующей обработки потока событий
- битовые карты (Bitmaps), которые можно использовать для реализации фильтра Блума или экономного отслеживания пользователей онлайн
- битовые поля (Bitfields), которые являются более гибкой альтернативой простым битовым картам
- геоиндексы (Geospatial), которые удобно использовать для работы с координатами
Особенно привлекательным выглядит наличие атомарных операций в некоторых структурах.
Сами по себе эти встроенные структуры данных Redis уже предоставляют широкий спектр возможностей, однако дополнительные библиотеки, используя эти мощные строительные блоки, выводят разработку на новый уровень продуктивности. Только посмотрите, как много возможностей предоставляет, например, библиотека radisson, написанная на Java. Мы в свой проект затащили распределенную (кластерную) блокировку RedLock, которую используем, например, для fairless доступа к распределенному ресурсу, который не может из-за нехватки ресурсов обрабатывать более одного запроса единовременно.
Подведем итоги? Говорить о Redis только в контексте кэширования невозможно. Это in-memory хранилище благодаря своей гибкости можно удобно применить к решению самых разных бизнес-задач. Мы точно затащим Redis к себе в проект и, надеюсь, сможем применить его не только как банальный кэш.
👍9
Слоистая архитектура, часть 1
Если взять любой мой микросервис (МС), то он будет построен в соответствии с многослойной (слоистой/многоуровневой) архитектурой. Это классические для web-приложения на Spring слои: Controller, Service, Repository. Большинство разработчиков на Spring научились делить приложение на слои, обращаться только к нижележащим слоям (но не всегда строго к одному нижележащему) и узлам того же слоя без создания циклических зависимостей. В борьбе с циклическими зависимостями помогает сам фреймворк, выбрасывая логичное исключение: BeanCurrentlyInCreationException. Однако некоторые интервьюеры все же захотят узнать у вас, как обойти это ограничение (конечно, без помощи рефакторинга :). Такие извращенные способы можно найти, например, здесь.
Для того чтобы показать слои приложения наглядно, я пошел в интернеты. Картинка ниже (первая) показалась мне достаточно занятной. Она взята из статьи, которая рассказывает, в том числе о веб-фреймворках, но предлагаю переложить ее на рассматриваемые MVC-приложения. Для этого попрошу вас:
- узел, на который смотрит конец каждой стрелки, рассматривать как пользователя некоторой функциональности узла из нижележащего слоя
- не обращать внимания на подписи Component
- рассматривать Controller как API-слой сервиса, который предоставляет доступ к некоторому домену, а View — как слой, отвечающий за формирование ответов для отображения экранов на фронтах (смесью домена и UI)
- мысленно объединить все Repository в один слой, Service — в другой и т.д.
Если картинка настолько неточна, то как она вообще попала в статью? Мы в нашем подразделении разрабатываем преимущественно BFF (Backend For Front-end), поэтому мне хочется остановиться на объединении пунктиром слоев View и Controller, так как до недавнего времени эти слои у нас в сервисах были сцеплены воедино (в слой Controller). Каким образом это выражалось? В МСе могли быть перемешаны эндпоинты, которые отвечают за UI и за работу с доменом. Или хуже, когда один и тот же эндпоинт в системе использовался для обеих этих целей, что зачастую приводило к невозможности повторного их переиспользования. Объединение слоев View и Controller приводило к тому, что каша получилась не только на уровне этого объединенного слоя, но и на уровне Service, который стал по факту объединением уже известного нам Service и некоторой примеси Service View, которая представляет собой UI надстройку над доменом.
Если взять любой мой микросервис (МС), то он будет построен в соответствии с многослойной (слоистой/многоуровневой) архитектурой. Это классические для web-приложения на Spring слои: Controller, Service, Repository. Большинство разработчиков на Spring научились делить приложение на слои, обращаться только к нижележащим слоям (но не всегда строго к одному нижележащему) и узлам того же слоя без создания циклических зависимостей. В борьбе с циклическими зависимостями помогает сам фреймворк, выбрасывая логичное исключение: BeanCurrentlyInCreationException. Однако некоторые интервьюеры все же захотят узнать у вас, как обойти это ограничение (конечно, без помощи рефакторинга :). Такие извращенные способы можно найти, например, здесь.
Для того чтобы показать слои приложения наглядно, я пошел в интернеты. Картинка ниже (первая) показалась мне достаточно занятной. Она взята из статьи, которая рассказывает, в том числе о веб-фреймворках, но предлагаю переложить ее на рассматриваемые MVC-приложения. Для этого попрошу вас:
- узел, на который смотрит конец каждой стрелки, рассматривать как пользователя некоторой функциональности узла из нижележащего слоя
- не обращать внимания на подписи Component
- рассматривать Controller как API-слой сервиса, который предоставляет доступ к некоторому домену, а View — как слой, отвечающий за формирование ответов для отображения экранов на фронтах (смесью домена и UI)
- мысленно объединить все Repository в один слой, Service — в другой и т.д.
Если картинка настолько неточна, то как она вообще попала в статью? Мы в нашем подразделении разрабатываем преимущественно BFF (Backend For Front-end), поэтому мне хочется остановиться на объединении пунктиром слоев View и Controller, так как до недавнего времени эти слои у нас в сервисах были сцеплены воедино (в слой Controller). Каким образом это выражалось? В МСе могли быть перемешаны эндпоинты, которые отвечают за UI и за работу с доменом. Или хуже, когда один и тот же эндпоинт в системе использовался для обеих этих целей, что зачастую приводило к невозможности повторного их переиспользования. Объединение слоев View и Controller приводило к тому, что каша получилась не только на уровне этого объединенного слоя, но и на уровне Service, который стал по факту объединением уже известного нам Service и некоторой примеси Service View, которая представляет собой UI надстройку над доменом.
Слоистая архитектура, часть 2
Каждый МС берет на себя как минимум две ответственности. К чему это приводит? МСы, которые могли отвечать только за UI или только за домен — смешиваются в один слой. Конечно, аналитику или разработчику разобраться, какие МСы и как между собой взаимодействуют, становится сложнее. Это является причиной возникновения циклических зависимостей уже на уровне взаимодействия МСов (когда систему нельзя представить в виде направленного ациклического графа). Такую систему очень тяжело поддерживать и масштабировать.
Получилось так, что архитектура МСа повлияла на архитектуру системы. Исходно в системе имела место ошибка проектирования, при которой произошло перемешивание слоев на всех уровнях. Требовалось выработать новый подход, чтобы наш BFF был поддерживаемым. К какой модели мы пришли (картинка ниже, вторая)?
view-api — это API, отвечающая за агрегацию доменных данных и вывод необходимой информации на экран клиенту. Требования к view-api предъявляются следующие:
- все эндпоинты view-api доступны только с фронтов, т.е. вызывать их из других API нельзя
- данные для отображения на экране, которые не приходят из core-api, должны прописываться в конфигурации view-api (текст, цвета кнопок, фона и др.)
- из view-api нельзя обращаться напрямую в back-системы
core-api — API, отвечающая за получение и обработку доменных данных с возможностью переиспользования. Требования к core-api предъявляются следующие:
- все ручки core-api недоступны с фронтов
- core-api ничего не знают про данные для отображения на экране (текст, цвета кнопок и фона), только если не проксируют эти данные с back-системы
- в идеале обращений между core-api тоже быть не должно
По результатам анализа системы мы пришли к строгой (обращение между узлами одного слоя или строго одного нижележащего слоя) многослойной архитектуре на уровне взаимодействия сервисов в системе. Теперь каждый сервис отвечает либо за работу с доменом, либо за UI. Мы развязали эти независимые модули/слои, которые ранее были скреплены, чтобы добиться лучшего повторного использования (особенно доменных сервисов). За счет чего достигается строгость? Фронты теперь могут обращаться только к view-api, view-api — только к core-api, а core-api — только к back-системам и друг к другу.
Каждый МС берет на себя как минимум две ответственности. К чему это приводит? МСы, которые могли отвечать только за UI или только за домен — смешиваются в один слой. Конечно, аналитику или разработчику разобраться, какие МСы и как между собой взаимодействуют, становится сложнее. Это является причиной возникновения циклических зависимостей уже на уровне взаимодействия МСов (когда систему нельзя представить в виде направленного ациклического графа). Такую систему очень тяжело поддерживать и масштабировать.
Получилось так, что архитектура МСа повлияла на архитектуру системы. Исходно в системе имела место ошибка проектирования, при которой произошло перемешивание слоев на всех уровнях. Требовалось выработать новый подход, чтобы наш BFF был поддерживаемым. К какой модели мы пришли (картинка ниже, вторая)?
view-api — это API, отвечающая за агрегацию доменных данных и вывод необходимой информации на экран клиенту. Требования к view-api предъявляются следующие:
- все эндпоинты view-api доступны только с фронтов, т.е. вызывать их из других API нельзя
- данные для отображения на экране, которые не приходят из core-api, должны прописываться в конфигурации view-api (текст, цвета кнопок, фона и др.)
- из view-api нельзя обращаться напрямую в back-системы
core-api — API, отвечающая за получение и обработку доменных данных с возможностью переиспользования. Требования к core-api предъявляются следующие:
- все ручки core-api недоступны с фронтов
- core-api ничего не знают про данные для отображения на экране (текст, цвета кнопок и фона), только если не проксируют эти данные с back-системы
- в идеале обращений между core-api тоже быть не должно
По результатам анализа системы мы пришли к строгой (обращение между узлами одного слоя или строго одного нижележащего слоя) многослойной архитектуре на уровне взаимодействия сервисов в системе. Теперь каждый сервис отвечает либо за работу с доменом, либо за UI. Мы развязали эти независимые модули/слои, которые ранее были скреплены, чтобы добиться лучшего повторного использования (особенно доменных сервисов). За счет чего достигается строгость? Фронты теперь могут обращаться только к view-api, view-api — только к core-api, а core-api — только к back-системам и друг к другу.
Кто готов посмотреть битву 20$ / month ChatGPT 4 против Completely Free Llama-3?
Попробуем отрефакторить один и тот же метод на языке Kotlin с помощью двух этих моделей здесь.
Попробуем отрефакторить один и тот же метод на языке Kotlin с помощью двух этих моделей здесь.
👨💻4👍2
Рефакторинг. Выделение метода, часть 1
Кажется, все знают, что функции/методы нужно делать короче. Вот как восторгается Роберт Мартин программе, написанной с помощью лаконичных функций:
… каждая функция в программе … занимала всего две, три или четыре строки. Все функции были предельно очевидными. Каждая функция излагала свою историю, и каждая история естественным образом подводила вас к началу следующей истории. Вот какими короткими должны быть функции!
Давайте разберем этот абзац, чтобы понять, как написать программу, которая порадовала бы Роберта Мартина. В первую очередь — это следовать SRP, однако не нужно ударяться в крайности, начиная делить код на множество микроскопических функций. Важно, чтобы те операции, которые вы выносите в функцию, находились на одном и том же уровне абстракции и являлись семантически независимыми (по отношению к остальному коду) и значимыми. Именно в таком случае функции будут представлять из себя строительные блоки, из которых может быть построена поддерживаемая программа. А дальше все просто: будучи носителями одной ответственности/семантики таким функциям несложно будет дать ясное и точное имя.
Кажется, все знают, что функции/методы нужно делать короче. Вот как восторгается Роберт Мартин программе, написанной с помощью лаконичных функций:
… каждая функция в программе … занимала всего две, три или четыре строки. Все функции были предельно очевидными. Каждая функция излагала свою историю, и каждая история естественным образом подводила вас к началу следующей истории. Вот какими короткими должны быть функции!
Давайте разберем этот абзац, чтобы понять, как написать программу, которая порадовала бы Роберта Мартина. В первую очередь — это следовать SRP, однако не нужно ударяться в крайности, начиная делить код на множество микроскопических функций. Важно, чтобы те операции, которые вы выносите в функцию, находились на одном и том же уровне абстракции и являлись семантически независимыми (по отношению к остальному коду) и значимыми. Именно в таком случае функции будут представлять из себя строительные блоки, из которых может быть построена поддерживаемая программа. А дальше все просто: будучи носителями одной ответственности/семантики таким функциям несложно будет дать ясное и точное имя.
👍3
Рефакторинг. Выделение метода, часть 2
Следование SRP выглядит несложно, однако всегда ли мы можем добиться его выполнения? Если вычисления в большом методе идут последовательно, где результаты каждого предыдущего этапа передаются следующему, как по конвейеру, то проблем не возникает. Но что, если это автономные потоки вычислений, которые многократно пронизывают друг друга в процессе прохождения по функции? На ум сразу приходят проекты, в которых используются библиотеки с низким уровнем абстракции. В данном случае, например, пользовательский поток переплетается с техническим, необходимым для управления работой клиента.
Что еще можно привести в качестве примеров? Конечно, использование сквозной функциональности:
- поэтапные логирование и аудит;
- замеры производительности отдельных участков кода и сбор метрик;
- и т.д.
Вот, например, поток аудита переплетается с бизнес-функциональностью подтверждения оффера:
Этот код можно было бы также оформить в виде конечного автомата, однако такое решение кажется здесь избыточным из-за малого числа состояний. Еще одним вариантом разделения двух несвязанных семантик в данном примере может быть применение техник АОП, которые однако не славятся простотой чтения и поддержки.
Что если в препарируемом методе нет автономных потоков вычислений, но и выделение новых методов кажется невозможным? Вероятно, в таком случае мы имеем дело с плохо написанным, запутанным кодом, который требует предварительной подготовки с использованием других техник рефакторинга.
Следование SRP выглядит несложно, однако всегда ли мы можем добиться его выполнения? Если вычисления в большом методе идут последовательно, где результаты каждого предыдущего этапа передаются следующему, как по конвейеру, то проблем не возникает. Но что, если это автономные потоки вычислений, которые многократно пронизывают друг друга в процессе прохождения по функции? На ум сразу приходят проекты, в которых используются библиотеки с низким уровнем абстракции. В данном случае, например, пользовательский поток переплетается с техническим, необходимым для управления работой клиента.
Что еще можно привести в качестве примеров? Конечно, использование сквозной функциональности:
- поэтапные логирование и аудит;
- замеры производительности отдельных участков кода и сбор метрик;
- и т.д.
Вот, например, поток аудита переплетается с бизнес-функциональностью подтверждения оффера:
suspend fun confirmOffer(...) {
...
try {
auditService.updateConfirmationStatus(auditReference, OperationStatus.PROGRESS)
offerService.confirmOffer(...)
auditService.updateConfirmationStatus(auditReference, OperationStatus.SUCCESS)
...
} catch (e: Exception) {
...
auditService.updateConfirmationStatus(auditReference, OperationStatus.FAIL)
...
}Этот код можно было бы также оформить в виде конечного автомата, однако такое решение кажется здесь избыточным из-за малого числа состояний. Еще одним вариантом разделения двух несвязанных семантик в данном примере может быть применение техник АОП, которые однако не славятся простотой чтения и поддержки.
Что если в препарируемом методе нет автономных потоков вычислений, но и выделение новых методов кажется невозможным? Вероятно, в таком случае мы имеем дело с плохо написанным, запутанным кодом, который требует предварительной подготовки с использованием других техник рефакторинга.
👍2
Предлагаю на примере Apache Kafka рассмотреть, какие есть подводные камни при сборе и использовании метрик. Не пугайтесь, если вы не знакомы с Kafka — эти идеи могут всплыть при мониторинге абсолютно любой системы.
👍2
Обзор DevSecOps, часть 1
Продолжаем тему Security при разработке приложений. На этой неделе проходил обучение по теме безопасной разработки ПО. Хочу поделиться с вами некоторой выжимкой.
Так полюбившиеся бизнесу гибкие методологии разработки (на основе Agile-манифеста) сильно повлияли на процессы разработки ПО в целом и на подход к информационной безопасности в частности. Как это выражается? Мы не сможем планомерно отдавать “безопасные” релизы, если будем следовать моделям “предыдущих поколений”, например Waterfall. Так появился Манифест Agile Security (Agile Security Manifesto). Его приоритеты естественным образом вытекают из принципов модели Agile:
- опора на разработчиков и тестировщиков, а не на специалистов безопасности
- проверка на безопасность в процессе, а не после завершения рабочего цикла
- безопасная реализация функциональности, а не добавление функций безопасности
- снижение рисков, а не поиск уязвимостей
Тут стоит отметить, что мы не упраздняем позицию “безопасник” в нашей структуре, а пытаемся с самого начала делать защищенный продукт за счет интеграции элементов Application Security (Sec) во все фазы цикла Development & Operations (DevOps).
Продолжаем тему Security при разработке приложений. На этой неделе проходил обучение по теме безопасной разработки ПО. Хочу поделиться с вами некоторой выжимкой.
Так полюбившиеся бизнесу гибкие методологии разработки (на основе Agile-манифеста) сильно повлияли на процессы разработки ПО в целом и на подход к информационной безопасности в частности. Как это выражается? Мы не сможем планомерно отдавать “безопасные” релизы, если будем следовать моделям “предыдущих поколений”, например Waterfall. Так появился Манифест Agile Security (Agile Security Manifesto). Его приоритеты естественным образом вытекают из принципов модели Agile:
- опора на разработчиков и тестировщиков, а не на специалистов безопасности
- проверка на безопасность в процессе, а не после завершения рабочего цикла
- безопасная реализация функциональности, а не добавление функций безопасности
- снижение рисков, а не поиск уязвимостей
Тут стоит отметить, что мы не упраздняем позицию “безопасник” в нашей структуре, а пытаемся с самого начала делать защищенный продукт за счет интеграции элементов Application Security (Sec) во все фазы цикла Development & Operations (DevOps).
👍3
Обзор DevSecOps, часть 2
Обратимся к графическому представлению DevSecOps и кратко пробежимся по предложенным методам.
SAST — это метод поиска уязвимостей в приложении, основанный на анализе исходного кода без его фактического выполнения. Как любой уважающий себя статический анализатор, он быстро выполняет анализ кода происходит без запуска приложения, но может выдавать как ложноположительные, так и ложноотрицательные срабатывания.
Для проверки на наличие известных уязвимостей и анализа лицензионной чистоты подключаемых компонентов используют:
- на этапе дизайна — OSA (Open-Source Analysis)
- на этапе сборки — SCA (Software Composition Analysis)
Пример опасных уязвимостей, которые может выявить инструмент SCA:
- log4shell – критическая уязвимость в библиотеке log4j, позволяющая запускать неавторизованный удалённый код
- spring4shell – критическая уязвимость во фреймворке Spring, которая позволяет запускать произвольный код на сервере (RCE)
Инструментальные средства DAST анализируют приложения без доступа к их исходному коду, то есть методом «черного ящика». В отличие от SAST, менее привязан к языку программирования, на котором написано приложение, а также позволяет проверить влияние окружения, нюансов развернутой БД, сервера и прочих деталей системы. Конечно, вручную придется разбирать большое число ложных срабатываний.
Более распространенным термином для AppSec.Hub является ASOC (Application Security Orchestration & Correlation). ASOC-платформа обеспечивает оркестрацию проверок и работу с их результатами на всем протяжении жизненного цикла ПО.
Для полноты картины отмечу, что для анализа уровня безопасности мобильных программ используется специальный набор практик и методов тестирования – MAST (Mobile Application Security Testing).
В блоке BCA есть упоминание Container Analysis, который совместно с BAST в случае оркестрации контейнеризированных приложений образуют Container Security (также встречается термин SecOps) — это непрерывный процесс и комплексное мероприятие по обеспечению безопасности контейнеризированных приложений на всех этапах их жизненного цикла, а также платформ и систем, сопровождающих программные продукты. Процесс частично включает в себя практики SAST, SCA, DAST, Security Compliance и Observability. На данный момент основной тулсет составляют решения классов:
IaC scanner — сканирование файлов конфигураций бизнес — и/или инфраструктурных сервисов. Например, docker file, yaml-манифесты, ansible роли и т.д. Чаще всего также имеет функциональность поиска cve используемых компонент и небезопасного хранения секретов
Policy Engine/Admission Controller — контроллер и sec gate для ресурсов при деплое и в runtime микросервисов. Позволяет определять несоответствие спецификациям ИБ, блокировать поставки, изменять конфигурации или создавать дополнительные ресурсы
CSP (Container Security Platform) — обсервабилити решение с прицелом на ИБ функции
LSM — Linux Security Modules а также eBPF-based Security Observability and Runtime Enforcement. Данный класс инструментов направлен на ограничение процессов совершать определенные syscall
Наконец WAF — это аппаратное устройство или программное обеспечение, которое анализирует и фильтрует весь входящий и исходящий HTTP(S)-трафик.
Конечно, все это сверху должно быть приправлено пентестами и программой Bug Bounty для, действительно, комплексной проверки выстроенных мер защиты.
Обратимся к графическому представлению DevSecOps и кратко пробежимся по предложенным методам.
SAST — это метод поиска уязвимостей в приложении, основанный на анализе исходного кода без его фактического выполнения. Как любой уважающий себя статический анализатор, он быстро выполняет анализ кода происходит без запуска приложения, но может выдавать как ложноположительные, так и ложноотрицательные срабатывания.
Для проверки на наличие известных уязвимостей и анализа лицензионной чистоты подключаемых компонентов используют:
- на этапе дизайна — OSA (Open-Source Analysis)
- на этапе сборки — SCA (Software Composition Analysis)
Пример опасных уязвимостей, которые может выявить инструмент SCA:
- log4shell – критическая уязвимость в библиотеке log4j, позволяющая запускать неавторизованный удалённый код
- spring4shell – критическая уязвимость во фреймворке Spring, которая позволяет запускать произвольный код на сервере (RCE)
Инструментальные средства DAST анализируют приложения без доступа к их исходному коду, то есть методом «черного ящика». В отличие от SAST, менее привязан к языку программирования, на котором написано приложение, а также позволяет проверить влияние окружения, нюансов развернутой БД, сервера и прочих деталей системы. Конечно, вручную придется разбирать большое число ложных срабатываний.
Более распространенным термином для AppSec.Hub является ASOC (Application Security Orchestration & Correlation). ASOC-платформа обеспечивает оркестрацию проверок и работу с их результатами на всем протяжении жизненного цикла ПО.
Для полноты картины отмечу, что для анализа уровня безопасности мобильных программ используется специальный набор практик и методов тестирования – MAST (Mobile Application Security Testing).
В блоке BCA есть упоминание Container Analysis, который совместно с BAST в случае оркестрации контейнеризированных приложений образуют Container Security (также встречается термин SecOps) — это непрерывный процесс и комплексное мероприятие по обеспечению безопасности контейнеризированных приложений на всех этапах их жизненного цикла, а также платформ и систем, сопровождающих программные продукты. Процесс частично включает в себя практики SAST, SCA, DAST, Security Compliance и Observability. На данный момент основной тулсет составляют решения классов:
IaC scanner — сканирование файлов конфигураций бизнес — и/или инфраструктурных сервисов. Например, docker file, yaml-манифесты, ansible роли и т.д. Чаще всего также имеет функциональность поиска cve используемых компонент и небезопасного хранения секретов
Policy Engine/Admission Controller — контроллер и sec gate для ресурсов при деплое и в runtime микросервисов. Позволяет определять несоответствие спецификациям ИБ, блокировать поставки, изменять конфигурации или создавать дополнительные ресурсы
CSP (Container Security Platform) — обсервабилити решение с прицелом на ИБ функции
LSM — Linux Security Modules а также eBPF-based Security Observability and Runtime Enforcement. Данный класс инструментов направлен на ограничение процессов совершать определенные syscall
Наконец WAF — это аппаратное устройство или программное обеспечение, которое анализирует и фильтрует весь входящий и исходящий HTTP(S)-трафик.
Конечно, все это сверху должно быть приправлено пентестами и программой Bug Bounty для, действительно, комплексной проверки выстроенных мер защиты.
👍3
А у вас в компании построили DevSecOps?
Anonymous Poll
31%
👍
44%
спасибо, что есть хотя бы DevOps
19%
нам не нужно
6%
🙅♂️
🗿2
Варианты расширения функциональности классов и их влияние на модель данных в Kotlin [1/2]
Возьмем для рассмотрения простой пример. Есть библиотечный класс, который используется во многих местах в нашем сервисе для представления банковских карт пользователя:
Кое-где нам нужно получать приоритет (абсолютный вес) конкретного экземпляра для последующей сортировки и фильтрации.
Какие есть варианты реализации?
1️⃣ Наивный вариант — добавляем в исходный класс новое поле priority
Выглядит удобно: при вызове конструктора однократно выполняются вычисление и присваивание приоритета, после чего в программе это поле переиспользуем. В глаза бросаются сразу два ограничения:
❌ решение технически нереализуемо для закрытых для изменений third-party библиотек
❌ смешиваем технические и доменные поля в одном классе
Даже если мы имеем возможность модифицировать класс Card, то внесение в него нового поля, оторванного от домена, не пойдет на пользу модели данных. Стоит ли говорит о том, что глобальное расширение библиотечного класса усложнит аналитику и тестирование коллегам за пределами нашего сервиса. Также отметим, что приоритет карты даже в рамках одного сервиса нужен не везде.
2️⃣ Используем наследование или паттерн Декоратор (Decorator)
Если библиотечный класс помечен как open, то мы можем использовать наследование, иначе — декоратор. Какие здесь могут быть ограничения? Если область применения вновь созданных классов носит локальный характер, то мы получим колоссальный объем избыточного кода.
✅ подходит для расширения даже third-party библиотек
❗️для решения локальных задач создание новых классов может быть нецелесообразно
С учетом наших вводных создание наследника или декоратора класса Card выглядит неуместным.
Возьмем для рассмотрения простой пример. Есть библиотечный класс, который используется во многих местах в нашем сервисе для представления банковских карт пользователя:
data class Card(val id: String, ...)
Кое-где нам нужно получать приоритет (абсолютный вес) конкретного экземпляра для последующей сортировки и фильтрации.
Какие есть варианты реализации?
1️⃣ Наивный вариант — добавляем в исходный класс новое поле priority
Выглядит удобно: при вызове конструктора однократно выполняются вычисление и присваивание приоритета, после чего в программе это поле переиспользуем. В глаза бросаются сразу два ограничения:
❌ решение технически нереализуемо для закрытых для изменений third-party библиотек
❌ смешиваем технические и доменные поля в одном классе
Даже если мы имеем возможность модифицировать класс Card, то внесение в него нового поля, оторванного от домена, не пойдет на пользу модели данных. Стоит ли говорит о том, что глобальное расширение библиотечного класса усложнит аналитику и тестирование коллегам за пределами нашего сервиса. Также отметим, что приоритет карты даже в рамках одного сервиса нужен не везде.
2️⃣ Используем наследование или паттерн Декоратор (Decorator)
Если библиотечный класс помечен как open, то мы можем использовать наследование, иначе — декоратор. Какие здесь могут быть ограничения? Если область применения вновь созданных классов носит локальный характер, то мы получим колоссальный объем избыточного кода.
✅ подходит для расширения даже third-party библиотек
❗️для решения локальных задач создание новых классов может быть нецелесообразно
С учетом наших вводных создание наследника или декоратора класса Card выглядит неуместным.
Варианты расширения функциональности классов и их влияние на модель данных в Kotlin [2/2]
3️⃣ Используем функции расширения
Kotlin предоставляет возможность расширять класс или интерфейс новой функциональностью без необходимости наследования от класса или использования шаблонов проектирования, таких как Decorator. Это делается с помощью специальных объявлений, называемых функциями расширения (extension functions):
Такие функции можно вызывать обычным способом, как если бы они были методами исходного класса, однако все же потребуется их импортировать явно:
✅ подходит для расширения даже third-party библиотек
✅ минимальное количество нового кода, который не выходит за пределы сервиса
⚠️ доступ к методам, расширяющим исходную модель, ограничен только необходимостью явного импорта
❗️потребовалось введение внешнего хранилища
Несмотря на то, что мы ввели внешнее хранилище, мы оказываем незначительное влияние на исходную модель данных. Можно модифицировать этот код так, чтобы вызовы setPriority и getPriority можно было делать только в явно ограниченном контексте. Для этого можно использовать вложенные классы или функции с приемом контекста. Вот пример использования вложенного класса:
Вот как будет выглядеть вызов:
Пример с использованием функции, которая принимает контекст можно посмотреть здесь.
В чем отличие от исходного варианта?
✅ доступ к методам, расширяющим исходную модель, ограничен в коде явно
⚠️ требуется вызов конструктора на каждый возов логики
Единственным минусом последнего решения могу назвать то, что если не знать об уже имеющейся реализации, то без должного анализа кода сервиса можно не нарочно создать дублирующую реализацию. IntelliJ IDEA, например, вам никак не сообщит о наличии таковой.
📝 Какой можем сделать вывод? Разные технические решения по-разному влияют на модель данных в вашей программе. Руководствуйтесь принципом разумной достаточности при внесении изменений, обращая наибольшее внимание на модификацию библиотечных моделей и корневых классов в иерархии наследования.
3️⃣ Используем функции расширения
Kotlin предоставляет возможность расширять класс или интерфейс новой функциональностью без необходимости наследования от класса или использования шаблонов проектирования, таких как Decorator. Это делается с помощью специальных объявлений, называемых функциями расширения (extension functions):
package edu.some
object CardPriority {
private val priorityMap = WeakHashMap<Card, Int>()
fun Card.setPriority(priority: Int) {
priorityMap[this] = priority
}
fun Card.getPriority(): Int? {
return priorityMap[this]
}
}
Такие функции можно вызывать обычным способом, как если бы они были методами исходного класса, однако все же потребуется их импортировать явно:
package edu.other
import edu.some.CardPriority.getPriority
import edu.some.CardPriority.setPriority
fun main() {
card.setPriority(2)
println("Card priority is ${card.getPriority()}")
}
✅ подходит для расширения даже third-party библиотек
✅ минимальное количество нового кода, который не выходит за пределы сервиса
⚠️ доступ к методам, расширяющим исходную модель, ограничен только необходимостью явного импорта
❗️потребовалось введение внешнего хранилища
Несмотря на то, что мы ввели внешнее хранилище, мы оказываем незначительное влияние на исходную модель данных. Можно модифицировать этот код так, чтобы вызовы setPriority и getPriority можно было делать только в явно ограниченном контексте. Для этого можно использовать вложенные классы или функции с приемом контекста. Вот пример использования вложенного класса:
package edu.some
object CardPriority {
private val priorityMap = WeakHashMap<Card, Int>()
class Context {
fun Card.setPriority(priority: Int) {
priorityMap[this] = priority
}
fun Card.getPriority(): Int? {
return priorityMap[this]
}
}
fun withCardPriorityContext(action: Context.() -> Unit) {
Context().action()
}
}
Вот как будет выглядеть вызов:
package edu.other
import edu.some.CardPriority.withCardPriorityContext
fun main() {
...
withCardPriorityContext {
card.setPriority(2)
println("Card ${card.id} is ${card.getPriority()} priority")
}
// cледующие вызовы не скомпилируются, так как находятся вне контекста
// card.setPriority(2)
// println("Card priority is ${card.getPriority()}")
}
Пример с использованием функции, которая принимает контекст можно посмотреть здесь.
В чем отличие от исходного варианта?
✅ доступ к методам, расширяющим исходную модель, ограничен в коде явно
⚠️ требуется вызов конструктора на каждый возов логики
Единственным минусом последнего решения могу назвать то, что если не знать об уже имеющейся реализации, то без должного анализа кода сервиса можно не нарочно создать дублирующую реализацию. IntelliJ IDEA, например, вам никак не сообщит о наличии таковой.
📝 Какой можем сделать вывод? Разные технические решения по-разному влияют на модель данных в вашей программе. Руководствуйтесь принципом разумной достаточности при внесении изменений, обращая наибольшее внимание на модификацию библиотечных моделей и корневых классов в иерархии наследования.
Сходил в гости к Володе, автору каналу канала System Design World. Проектировали файловое хранилище. Оцените, получилось гуд 😆 или супер гуд 😃 ? Прямая ссылка на YT.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3⚡1
Как вам результат? Хотел написать пост, когда счетчик достигнет 20, но в последнее время отдавал приоритет менти, которые приходят на повторные консультации. Самый крутой результат из этих ребят — джавист с 1 годом коммерческого опыта повысил ЗП со 100 до 265 т.р. на руки, устроившись в финтех на позицию middle. Если вы до сих пор топчитесь на месте по своим карьерным целям, то рекомендую все же заскочить ко мне на бесплатную часовую консультацию. Многим этого более чем достаточно, чтобы сдвинуться с мертвой точки, а иногда и покорить очередную неприступную вершину.
getmentor.dev
Максим Гусев | GetMentor – открытое сообщество IT-наставников
Java Tech Lead @ Альфа-Банк | GetMentor – это открытое комьюнити IT-наставников, готовых делиться своими опытом и знаниями. Наша задача – помогать людям находить ответы на свои вопросы в работе или жизни через прямой доступ к экспертизе в разговоре 1-на-1.
👍1