Жуткая история одного пользователя
На днях я столкнулся с весьма загадочным и мистическим явлением в лучших традициях сериала «Секретные материалы». Аж кровь в жилах застыла. В общем, удалили пользователя из системы, а душа его осталась плутать по закоулкам и пугать все входящие API запросы. Да так, что сторонние системы, которые эти запросы слали, в ужасе шарахались и бросали NullPointerException. Если не хотите спать сегодня ночью, то смело заглядывайте сюда и узрите сию жуткую историю.
Произошло это, как и куча всякого другого дерьма, в 2020 году.
У меня была интеграция, разработкой которой я занимался, и сторонняя система, API которой дергала моя интеграция. Чтобы не путаться, давайте назовем систему Системой, а интеграцию – ни много ни мало, Интеграцией.
Интеграция моя отвечала за то, чтобы получать на вход email и создавать в Системе пользователей с этим email и другими заданными атрибутами. Если же пользователь уже существовал в Системе, Интеграция должна была получить его по айди и присвоить эти самые атрибуты. Проблема заключалась в том, что айди пользователя нам заранее неизвестен, и никаких способов получения пользователя по email Система не предоставляла. Поэтому мне приходилось итерироваться по всем пользователям в Системе (благо, их там довольно мало, и сильно много их стать не должно) и искать совпадающий email:
users.stream().filter(user -> user.getAccount().getEmail().equals(email)).findFirst();
Особых проблем это не доставляло, и Система возвращала ответ в следующем формате:
"Users": [{
"id": 42,
"displayName": "Artem Krutov",
"account": {
"id": 42,
"username": "artem.krutov",
"email": "user@email.com"
}
},
{
...
}]
Как видно, возвращался нам массив пользователей, и email каждого из них был доступен во внутренней структуре account
.
Тщательно протестировав Интеграцию и прогнав несколько реальных сценариев, я отправился на заслуженный отдых.
И тогда это началось…
Шутка, началось все тогда, когда я через интерфейс Системы удалил созданного тестового юзера, чтобы он не путался под ногами, да и в целом, чтобы его никто не видел. А вот когда я его удалил… душа его осталась бродить по длинным и темным коридорам системы.
До поры до времени все было хорошо. Но когда один из пользователей был удален в Системе, и его темная душа начала пугать мои запросы, моя Интеграция начала себя странно вести и бросаться ошибками. Проблема была все в той же строке с итерированием:
users.stream().filter(user -> user.getAccount().getEmail().equals(email)).findFirst();
А все потому, что душа удаленного пользователя все еще присутствовала в ответе Системы:
{
"id": 42,
"displayName": "Artem Krutov",
"account": {
"id": 42,
"username": "deleted-42",
"email": null
}
}
Видите? Данные пользователя обнулились, но он все равно возвращается в ответе. И, так как Интеграция предполагает, что раз мы нашли пользователя, то должен быть и его уникальный идентификатор в виде email, а его не оказалось, падал NPE:
java.lang.NullPointerException: null
Это выглядит довольно стремно и костыльно. Зачем возвращать мертвого обнуленного юзера, с которым никак нельзя работать? Но из всего этого можно вынести простую мораль:
КОГДА ВЫ РАБОТАЕТЕ СО СТОРОННИМИ СИСТЕМАМИ, ОЖИДАЙТЕ ОТ НИХ ПОДСТАВ
Они могут изменить свое поведение и наказать вас в любой момент. Конечно, суть проблемы в том, что я не защитился от этой ошибки изначально. Если бы мой код предполагал, что подстава может случиться, он бы выглядел следующим образом:
if (users != null) {
return users.stream().filter(user -> {
if (user != null && user.getAccount() != null && user.getAccount().getEmail() != null) {
return user.getAccount().getEmail().equals(email);
}
return false;
}).findFirst();
}
return Optional.empty();
Всегда относитесь к сторонним системам с подозрением, ибо они в любой момент могут начать слать что-то неожиданное. Проверяйте полученные данные и отвергайте невалидные ответы. Обезопасьте себя от плохих ответов заранее, а не тратьте время на анализ логов, как это сделал я!
Понравилось? Подписывайтесь на меня в соцсетях!
Кошмар! Впервые вижу настолько костыльное решение для удаления) Системный аналитик во мне негодует, а воображение рисует все трудности отлавливания таких багов. Бррр
При сравнении данных в Java всегда лучше вызывать equals от той переменой, от которой есть уверенность что она не null. По крайней мере, я выработал эту привычку у себя. Конечно, это не спасет если аккаунт например null, но зачастую в 90% случаев помогает.
Это правильная практика, и такая конструкция действительно не приводила бы к ошибке в данном случае:
Спасибо за отзыв!
buy levitra internet
buy levitra internet
viagra para mujer
viagra para mujer
pharmacy2u levitra
pharmacy2u levitra
tadalafil 5mg para que sirve
tadalafil 5mg para que sirve
sildenafil 45 mg
sildenafil 45 mg
sildenafil side effects long term
sildenafil side effects long term