Жуткая история одного пользователя
На днях я столкнулся с весьма загадочным и мистическим явлением в лучших традициях сериала «Секретные материалы». Аж кровь в жилах застыла. В общем, удалили пользователя из системы, а душа его осталась плутать по закоулкам и пугать все входящие 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
whats better sildenafil or tadalafil
whats better sildenafil or tadalafil
buy tramadol us pharmacy
buy tramadol us pharmacy
buy levitra at
buy levitra at
how long does cialis work
how long does cialis work
free coupons for levitra
free coupons for levitra
hims sildenafil review
hims sildenafil review
levitra vardenafil
levitra vardenafil
levitra 10 mg filmtabletten vardenafil
levitra 10 mg filmtabletten vardenafil
sildenafil before and after
sildenafil before and after
cialis blood pressure side effects
cialis blood pressure side effects
sildenafil over the counter cvs
sildenafil over the counter cvs
ribavirin online pharmacy
ribavirin online pharmacy
cheapest online pharmacy india
cheapest online pharmacy india
sildenafil vs tadalafil which is better
sildenafil vs tadalafil which is better
erectile dysfunction medications
erectile dysfunction medications
what is the difference between tadalafil and sildenafil
what is the difference between tadalafil and sildenafil
carbamazepine and children
carbamazepine and children
is celebrex an opioid
is celebrex an opioid
tegretol and magnesium deficiency
tegretol and magnesium deficiency
taking motrin and excedrin together
taking motrin and excedrin together
elavil imitrex
elavil imitrex
cilostazol in renal failure
cilostazol in renal failure
indomethacin medicines.ie
indomethacin medicines.ie
mestinon overdose in dogs
mestinon overdose in dogs
can you get high off elavil
can you get high off elavil
what is diclofenac sodium gel used for
what is diclofenac sodium gel used for
can you take meloxicam and lyrica together
can you take meloxicam and lyrica together
azathioprine for celiac disease
azathioprine for celiac disease
maxalt copay assistance card
maxalt copay assistance card
lioresal strengths
lioresal strengths
piroxicam prati donaduzzi
piroxicam prati donaduzzi
allergic reaction to sumatriptan
allergic reaction to sumatriptan
what do periactin tablets do
what do periactin tablets do
is flexeril or tizanidine stronger
is flexeril or tizanidine stronger
artane beaumont community centre
artane beaumont community centre
cyproheptadine for akathisia
cyproheptadine for akathisia
is zanaflex good
is zanaflex good