#программирование

Жуткая история одного пользователя

На днях я столкнулся с весьма загадочным и мистическим явлением в лучших традициях сериала «Секретные материалы». Аж кровь в жилах застыла. В общем, удалили пользователя из системы, а душа его осталась плутать по закоулкам и пугать все входящие 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
NullPointerException

Это выглядит довольно стремно и костыльно. Зачем возвращать мертвого обнуленного юзера, с которым никак нельзя работать? Но из всего этого можно вынести простую мораль:

КОГДА ВЫ РАБОТАЕТЕ СО СТОРОННИМИ СИСТЕМАМИ, ОЖИДАЙТЕ ОТ НИХ ПОДСТАВ

Они могут изменить свое поведение и наказать вас в любой момент. Конечно, суть проблемы в том, что я не защитился от этой ошибки изначально. Если бы мой код предполагал, что подстава может случиться, он бы выглядел следующим образом:

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();

Всегда относитесь к сторонним системам с подозрением, ибо они в любой момент могут начать слать что-то неожиданное. Проверяйте полученные данные и отвергайте невалидные ответы. Обезопасьте себя от плохих ответов заранее, а не тратьте время на анализ логов, как это сделал я!

Понравилось? Подписывайтесь на меня в соцсетях!

 
Twitter
VK
guest
3 Комментариев
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Татьяна Крутова
Татьяна Крутова
3 лет назад

Кошмар! Впервые вижу настолько костыльное решение для удаления) Системный аналитик во мне негодует, а воображение рисует все трудности отлавливания таких багов. Бррр

Руслан
Руслан
3 лет назад

При сравнении данных в Java всегда лучше вызывать equals от той переменой, от которой есть уверенность что она не null. По крайней мере, я выработал эту привычку у себя. Конечно, это не спасет если аккаунт например null, но зачастую в 90% случаев помогает.

Social media & sharing icons powered by UltimatelySocial
3
0
Нравится? Оставьте комментарий!x
()
x