ДисклеймерВсе публикации в данном блоге являются исключительно моими личными материалами, основанными на открытых источниках и на моём собственном опыте, анализе и выводах. Они не содержат и не могут содержать конфиденциальной, коммерческой или внутренней информации работодателя, клиентов, партнёров или любых иных организаций, с которыми у меня есть или были договорные либо иные юридические отношения.Любые компании и организации, с которыми я связан, прямо или косвенно, не несут никакой ответственности за содержание данного блога, не подтверждают изложенные здесь идеи и мнения. Публикации не могут рассматриваться как официальная позиция какой-либо компании. Всю ответственность за содержание несёт исключительно автор.@kodbaza ⚫️ #disclaimer @builin
КОДОВАЯ БАЗА_ ⚫ Блог программиста: архитектура, дизайн систем, алгоритмы и чистый код
@kodbaza
Блог о разработке и программированииАрхитектура, ООП, чистый код, Java, Spring Framework, Postgres и всё что около@builinЛичный блог автора. Все материалы публикуются от имени автора и не отражают чью-либо позицию.
Похожие каналы
Все →Последние посты
System Design Interview: НачалоНа собеседованиях по проектированию систем проверяют, насколько хорошо вы ориентируетесь в современном ландшафте высоконагруженных систем — балансировщики, очереди, SQL- и NoSQL-базы данных, реплицирование и шардирование — и ваши способности работать с требованиями, реагировать на изменение требований, умение отсечь лишние детали.Или — как вы заучили стандартные шаблоны задач для таких собеседований…В каком-то смысле эти собеседования пришли на смену Object-Oriented Design собеседованиям. System Design собеседования более универсальны, а ещё их проводят в Google — так они и стали популярны.К сожалению, разные компании и разные собеседующие оценивают кандидатов по-разному. Когда я проводил похожие собеседования, я больше обращал внимание на способность решать задачи и в целом на умение анализировать архитектуру, укладывать её в диаграммы. Кто-то смотрит на знание деталей работы конкретных инструментов — вроде особенностей индексирования или партиционирования в разных СУБД. Кто-то ожидает получить заранее известное шаблонное решение.Поэтому желательно быть готовым ко всему. Секция System Design в виде отдельного собеседования, скорее всего, будет ждать вас в любой крупной компании, если ваша позиция — senior-разработчик или выше. В компаниях поменьше вас может ждать пара вопросов в рамках общей языковой секции — на понимание каких-то технологий или паттернов. В некоторых компаниях такие вопросы могут задавать даже junior-разработчику.Проводить такие собеседования должны инженеры соответствующей квалификации — lead-разработчики или архитекторы. Я встречал в одном российском бигтехе middle-разработчика, который проводил секцию System Design — он был из тех, кто ожидал точного шаблонного решения.⸻Отсюда первые советы:▪️Вам не нужна эта дисциплина, если ваш уровень ниже middle+. Не позиция на текущем рабочем месте, а именно ваша реальная квалификация или ваш уровень на рынке. Материала и технологий много, и чтобы проще в этом ориентироваться
Про верхние перцентилиИли процентили, это то же самое — percentiles. Или квантили. 99 перцентиль — это 0.99 квантиль.При мониторинге систем количество ошибок или время выполнения запросов обычно рассчитывают в перцентилях.Например, время отклика для p95 равное 1с означает, что 95% запросов выполняются быстрее чем за 1с, а 5% выполняются за 1с или дольше. Насколько угодно дольше.Возможно ваша система устроена так, что пользователь с большим количеством данных получает большую задержку. И вероятно, что этот пользователь приносит и большие деньги.Например, ему в премиум тарифе доступен большой объем хранящейся информации. Или, если речь идёт о магазине или финансах, он совершает много покупок и транзакций.Для бизнеса потеря таких клиентов может иметь серьёзные последствия. Поэтому нужно обращать внимание и на врехние (p99, p999) перцентили. А также принимать во внимание средние и даже максимальные показатели. Это база.@kodbaza ⚫️ #observability #monitoring #ux
Как ещё можно передать версию APIКонечно чаще всего используется префикс вида v1 в URL. Но вот вам ещё пара способов для расширения кругозора.HTTP-заголовокКлиент передает версию API, по которой хочет выполнить обмен, в HTTP-заголовке Accept: или другом подобном.Такой способ встречается в GitHub API v3 и в Stripe:Accept: application/vnd.github.v3+jsonStripe-Version: <date>Хранение версии API на стороне сервераЕсли клиент при запросах авторизуется по токену, то можно хранить используемую им версию API на сервере и дать возможность выбирать версию в UI административной панели.Этот способ тоже используется в Stripe для выбора версии API по умолчанию для новых токенов.@kodbaza ⚫️ #rest #api
Что такое версия APIЭто прям база-база. Наверняка когда вы в первый раз увидели URL вида /api/v1/item/{id}, вы задумались "что такое v1?"Даже в базовых уроках по разработке API зачастую рекомендуют при указании URL сразу добавить в него версию v1.Версия API напрямую связана с совместимость клиента и сервера при RESTful взаимодействии.Вы можете без проблем развивать свой API, пока ваши изменения обратно совместимы по запросам от клиентов к серверу и прямо совместимы по ответам сервера клиенту (см. предыдущий пост).Но когда бизнес-требования невозможно выполнить без потери совместимости — интеграция сломается без обновления всех клиентов.Проблема решается добавлением новой версии API (v1 > v2). Обновленные клиенты могут перейти на новую версию, старые клиенты могут остаться на старой версии — никаких сбоев. Но любое архитектурное решение несёт компромиссы. Расплата — теперь вы как поставщик API должны поддерживать все версии API.В следующем посте расскажу про два других способа указывать версию API при запросе.За репост — плюс в карму 🤓@kodbaza ⚫️ #rest #api #url
Прямая и обратная совместимость в обмене сообщениямиВ системах обмена сообщениями (например, через Kafka, RabbitMQ, REST API) формат данных со временем меняется. Чтобы обновления сервисов не приводили к сбоям, важно понимать два ключевых вида совместимости — прямую и обратную. Мои менти часто их путают, поэтому захотел написать об этом.Прямая совместимость (forward compatibility)Это когда старые версии клиентов могут читать сообщения, созданные новыми версиями продюсеров. Например, если в схему добавили новое поле, старый клиент его просто игнорирует и продолжает работать без ошибок.Обратная совместимость (backward compatibility)Это когда новые версии клиентов могут читать сообщения, созданные старыми версиями продюсеров. Например, новый клиент ожидает дополнительное поле, но если его нет (потому что продюсер ещё старой версии), клиент корректно обрабатывает отсутствие этого поля. Например, заполняет его значением по умолчанию.Полная совместимостьКогда выполняются оба условия — новые и старые клиенты могут без проблем читать сообщения друг друга.В реальных условиях обновление всех сервисов одновременно — редкость. Обеспечение совместимости позволяет дорабатывать и обновлять сервисы постепенно без простоев и ошибок.Основные правила обеспечения совместимости:1⃣ Добавляйте новые поля со значениями по умолчанию (например, null).2⃣ Не удаляйте и не переименовывайте существующие поля.3⃣ Избегайте изменений типов данных. В том числе, избегайте изменения значений в перечислениях (enum).4⃣ При несовместимых изменениях используйте версионирование сообщений. Это позволяет одновременно поддерживать несколько форматов.@kodbaza ⚫️ #microservices #distributed_systems
✅ Правильный ответ: CLSP требует, чтобы объекты подклассов могли использоваться везде, где ожидаются объекты суперкласса, без изменения корректности работы программы. Если клиентский код ожидает, что любой экземпляр Bird может летать, то подставляя Ostrich, он сталкивается с неожиданным исключением. Это нарушение LSP.Вывод: для соблюдения LSP одного наследования недостаточно, нужно реализовать ожидаемую логику.@kodbaza ⚫️ #solid #lsp
Небольшая задача про LSP из SOLID🔹 В проекте используется следующая иерархия классов:public class Bird { public void fly() { // реализация полёта }}public class Ostrich extends Bird { @Override public void fly() { throw new UnsupportedOperationException("Страусы не летают"); }}Этот код работает, но вызывает исключение при вызове fly() у экземпляра Ostrich. Почему это нарушает принцип подстановки Лисков (#LSP)?🔸 Выберите один правильный вариант:A. Потому что Ostrich не реализует интерфейс Flyable.B. Потому что подкласс не должен бросать исключения, которых нет в базовом классе.C. Потому что поведение Ostrich не совместимо с поведением Bird, ожидаемым клиентским кодом.D. Потому что метод fly() не финализирован в суперклассе.@kodbaza ⚫️ #quiz #solid #lsp
Файловые дескрипторы: о чем могут спросить на собеседовании?Файловый дескриптор — это просто числовой идентификатор, который операционная система использует, чтобы знать, с каким ресурсом вы работаете. Файл, сокет — это ресурсы, а дескриптор связывает ваше приложение с ними. Всё просто, но есть нюансы, которые стоит понимать.Во-первых, файловые дескрипторы — ограниченный ресурс. На практике это значит: если вы забудете закрыть файлы, даже в небольшом приложении вы рискуете столкнуться с ошибкой Too many open files. Неприятно, особенно на проде. Поэтому всегда используйте конструкцию try-with-resources или аналогичные механизмы.Во-вторых, работа с дескрипторами тесно связана с потоками ввода-вывода. Если на собеседовании зададут вопрос про NIO (Non-blocking IO), будьте готовы объяснить, как селектор позволяет обрабатывать множество сокетов с ограниченным числом дескрипторов. Это, кстати, одна из ключевых идей для высоконагруженных систем.☕ Поделитесь постом с коллегами, вдруг у них тоже на собеседовании спросят "А что вы знаете про файловые дескрипторы?" 😁@kodbaza ⚫ #io #nio #file #os
SRP vs. SLAP: как не перепутать?Когда только знакомишься с принципами чистого кода, SRP и SLAP легко перепутать. Оба про порядок и ясность. Но всё же это разные вещи. Давайте разберёмся.SRP — Один класс, одна задачаSingle Responsibility Principle (SRP) говорит: у класса должна быть только одна причина для изменения. То есть один класс — одна задача. Если ваш класс отвечает и за логику отображения, и за работу с базой, то рано или поздно будет беда. Разделяйте обязанности!SLAP — Один уровень абстракции на методSingle Level of Abstraction Principle (SLAP) про методы: не мешайте высокий и низкий уровни абстракции в одном месте. Например, если метод сначала вызывает сторонний сервис, потом парсит данные, а потом ещё что-то форматирует — это три разных уровня. Лучше разбить такой метод на три, где каждый будет заниматься своим делом.Запомнить можно так: SRP наводит порядок среди классов, SLAP — среди строк. Первый помогает избежать «многостаночников», второй — «спагетти-кода».@kodbaza ⚫ #cleancode #srp #slap
SLAP = Single Level of Abstraction PrincipleSLAP — звучит больно, да? На деле всё просто. Этот принцип спасает нас от путаницы в коде и помогает писать так, чтобы читать было приятно. Внезапно, как и остальные принципы. А теперь — по порядку.Что вообще значит «уровень абстракции»?Представьте: вы пишете метод. В идеале он должен решать одну задачу на одном уровне детализации. Если метод сначала делает SQL-запрос, потом парсит JSON и в конце форматирует дату — это, мягко говоря, каша.SLAP требует простотыКаждый метод должен «общаться» с кодом своего уровня. Если вы работаете с базой данных — вся логика на этом уровне. Если форматируете данные — то только это. Хотите всё вместе? Разбейте на методы! Вызвать несколько мелких функций всегда проще и понятней.А если не соблюдать?Код становится нечитаемым. Вы открываете метод, а там и низкоуровневые детали, и высокоуровневые решения. Тут же и запрос к БД выполнили и json-сообщение собрали. Чувствуете, как хочется закрыть этот файл и сделать кофе? Вот этого и надо избегать. В одном методе получить данные, в другом обработать.✋ SLAP — это про порядок и ясность. Чем проще и понятнее код, тем меньше шансов, что коллега захочет вас найти и «поговорить».@kodbaza ⚫ #cleancode #slap
Stream.toList() vs Stream.collect(Collectors.toList())Есть распространенное заблуждение, что метод Stream.toList() из JDK 16 - это более короткое написание существовавшего до него collect(Collectors.toList()).Весомая причина для использования collect(Collectors.toList()) в современной Java - иногда нужен мутабельный список, а этот коллектор возвращает обычный ArrayList. Хотя строго говоря иммутабельность не гарантируется.Современный Stream.toList() возвращает иммутабельный список, что точно гарантировано. Так что они не взаимозаменяемы.Stream.toList() это скорее аналог Collectors.toUnmodifiableList() (ссылка).@kodbaza ⚫️ #java #stream
Валидация сообщений KafkaИмеем классический CRUD сервис на Spring Boot - контроллер принимает сообщения по HTTP REST API. Перед этим сообщение валидируется в соответствии с расставленными аннотациями вроде @NotEmpty, а после передается в бизнес-слой для верификации данных и выполнения операции в БД.Нужно при получении аналогичного сообщения из топика Kafka выполнить те же валидации и те же операции в бизнес-слое с минимальными изменениями кода. Цель - переиспользовать имеющийся функционал, DRY и SRP.Пример с baeldung - не лучший способ. Spring позволяет настроить валидацию аннотациями. Нужно над классом, в котором расположен вызываемый метод, повесить @Validated и параметр метода пометить @Valid. Примера в сети не нашел, но этот способ описан в документации. А в этом примере таким же способом прям на параметры и результат метода валидации назначены.Не забудьте добавить в контекст тестов Spring Boot ValidationAutoConfiguration.class, если у вас используется не полный контекст.🍃 Отправь коллеге, держи его в курсе 😉@kodbaza ⚫ @spring @validation @kafka @rest
DYC = Document Your CodeДокументировать свой код — прекрасная практика, но все должно быть в меру и к месту.1⃣ Комментарии в коде — объясняют принятые в коде особенности и допущения реализации2⃣ Документирующие комментарии — заголовки методов и классов, которые компилируются в документ и парсятся IDE (например, javadoc)3⃣ Сопроводительная записка (README.md) — файл в корне проекта с описанием проекта и инструкцией по его сборке и запуску4⃣ Руководство пользователя — отдельный документ или сайт для пользователей приложения. Помните, что программисты, использующие вашу библиотеку, — тоже пользователи.Я выработал для себя следующие практики, которые не меняются уже лет 15:• Самодокументируемый код всегда лучше, хорошему коду не нужны комментарии.• Если мне хочется написать комментарий, я создаю в этом месте вызов нового метода или запись в лог. Текст комментария становится частью кода, в этом и заключается самодокументирование.• Код никогда не врёт, комментарии могут устареть. Чем меньше нужно поддерживать комментарии в коде, тем лучше.• Документирующие комментарии должны отвечать на вопрос "что делает метод или класс", а как он это делает должно быть понятно из кода.• Если в коде пришлось реализовать временный костыль или просто странное и сложное решение, я пишу в коде сопровождающий комментарий. В нем описываю причины такого решения и возможные варианты исправления, пишу TODO. Я пишу его в первую очередь для себя, мне не хочется через полгода заново тратить время на исследования.• Конвенция важнее понятного кода — код должен быть читаемым. Например, если имя метода слишком длинное, его надо либо переименовать, либо разбить на два метода. Если в компании решили, что комментарии и TODO в коде нельзя писать, нахожу для них место в комментариях к задаче или к коммиту.• README.md или его аналог должен обязательно описывать проект и содержать инструкцию по сборке и запуску. Не забывайте прикладывать README.md к решению тестового задания, иначе ваше решение могут вообще не
Правильный ответ:2, 3, 5, 6Идентификатор бина (имя) используется вместо квалификатора, если нет явного квалификатора.Если найдено несколько кандидатов для внедрения по типу и не указан квалификатор в месте внедрения, квалификатор определяется по имени поля.@kodbaza ⚫️ #java #spring #quiz #dependencyInjection