Безпека by design. Частина 7. (Спрощення стану)

12 вересня 2023 1 хвилина Автор: Lady Liberty

Змінний стан та проектування: Виклики та шляхи вирішення

Якщо не вдалося ефективно керувати змінним станом, це може призвести до різних проблем. Наприклад, це може призвести до серйозних проблем з безпекою літака, який вилітає з багажем пасажира, який ніколи не сідав на борт. Зі зростанням складності сутностей стає важче контролювати їх стан, особливо коли цей стан має багато складних переходів. В таких випадках необхідні шаблони дизайну, які допоможуть зменшити цю складність. Крім того, управління складними змінюваними станами сутностей може бути важко реалізовано в коді. Це особливо стає проблемою, коли різні частини коду намагаються одночасно змінювати одну і ту ж сутність.

Техніки, що контролюють цю складність, повинні вирішувати ці конфлікти. У нашому подальшому дослідженні ми розглянемо ці питання, розрізняючи однопотокові середовища, такі як контейнери EJB, від багатопотокових середовищ. У цьому розділі ми детально розглянемо чотири шаблони дизайну, які можуть допомогти спростити управління складністю. Перші два з них стосуються однопотокових середовищ: частково незмінних сутностей і об’єктів стану. Далі ми розглянемо знімки сутностей, які добре працюють в багатопотокових середовищах. У кінці ми розглянемо шаблон проектування “Entity Relay”, який спрямований на зниження концептуальної складності в будь-якому середовищі, як однопоточному, так і багатопоточному.

Починаємо

 

При першому знайомстві з об’єктно-орієнтованим програмуванням студенти вчаться реалізовувати сутності у вигляді об’єктів зі змінюваними полями даних. У Лістингу 7.1, наприклад, можна побачити, як оновлюється  поле  балансу і як перевірити, чи достатньо коштів на рахунку для захисту цієї операції.

Однак у багатопотоковому середовищі цей код не є безпечним. Уявіть, що у нас є рахунок на 100 доларів, з якого списуються дві суми одночасно: 75 доларів у банкоматі та 50 доларів у результаті автоматичного переказу грошей. Перевірка балансу, виконана під час другої процедури виведення, може відбутися раніше, ніж перша процедура встигне її зменшити.

Погляньте на цю послідовність подій.

  1. Процедура зняття банкомату перевіряє баланс ($100 > $75) – все в порядку, продовжуємо.

  2. Автоматична процедура переказу грошей перевіряє баланс ($100 > $50) – все в порядку, продовжуємо.

  3. Процедура зняття коштів у банкоматі розраховує новий баланс: $100 – $75 = $25.

  4. Процедура зняття коштів в банкоматі оновлює баланс – 25 $.

  5. Автоматична процедура переказу коштів розраховує новий баланс: $25 – $50 = -$25.

  6. Автоматична процедура грошових переказів оновлює баланс: -25 доларів.

Так як ці два потоки виконуються не послідовно один за одним, а одночасно, перевірка балансу була пропущена. У другій угоді баланс перевірявся до завершення першої, тому він ні на що не вплинув і не захистив від перевитрати витрат.

Це приклад так званого  расового стану. Ситуація може погіршитися: якщо події відбуваються порядку 1, 2, 3, 5, 4, 6, кінцеве сальдо буде невірним – 50 доларів. Незважаючи на те, що з рахунку було знято $125 при початковому балансі $100, на ньому все одно залишаються кошти (можна перевірити).

Щоб вирішити цю проблему, ми повинні або створити захисне середовище, яке забезпечить, щоб тільки один потік звертався до сутності в будь-який момент часу, або спроектувати сутності таким чином, щоб вони могли належним чином працювати з декількома одночасними потоками. Однопотокове середовище безпеки можна створити багатьма різними способами. Одним з найпростіших є виконання кожного клієнтського запиту, а потім завантаження об’єкта сутності в окремий потік. Таким чином, ви гарантуєте, що лише один потік працює з кожним екземпляром сутності. Якщо два потоки працюють з сутністю (даними) відразу, то два його екземпляри змінюються одночасно, кожен у своєму потоці. При цьому дозвіл конфліктів лягає на транзакційну систему бази даних.

Інший підхід полягає у використанні фреймворку типу Enterprise JavaBeans, EJB, який піклується про цикл завантаження/збереження сутності. У цьому випадку саме фреймворк гарантує, що тільки один потік може отримати доступ до сутності в будь-який момент часу, і в той же час мінімізує трафік до бази даних. Надання спільного доступу до інсталяції залежить від конфігурації. Напевно, найсучаснішим способом створення однопотокового середовища є використання системи акторів, такої як Акка. В Акці суб’єкт господарювання може перебувати всередині актора, який гарантує, що транзакційні потоки отримують доступ до нього по одному.

Багатопотокові середовища зазвичай використовуються, коли екземпляри сутностей зберігаються в спільному кеші, такому як Memcached, щоб уникнути взаємодії з базою даних. Коли потік хоче отримати доступ до сутності, він спочатку шукає його в кеші. У цьому випадку сутності повинні бути розроблені для коректної роботи навіть з декількома одночасними потоками. Традиційний (древній і стійкий до помилок) спосіб досягнення цього – додавання семафорів в код,  які синхронізують потоки один з одним. У Java реалізація даного підходу на нижчому рівні представлена  синхронізованим ключовим словом.

Вам доступно багато інших варіантів і рамок, але в будь-якому випадку гарантувати правильну поведінку буде непросто. Для початку спростимо змінні стани в однопотоковому середовищі за допомогою використання частково незмінних сутностей.

Частково незмінні сутності

Якщо дані можна змінювати, завжди є ризик, що якийсь фрагмент коду змінить їх. І іноді ці зміни можуть бути небажаними: наприклад, в код закралася помилка або хтось виявив уразливість і запустив атаку. Якщо окремі  компоненти, які модифікуються,  не є безпечними, має сенс зменшити кількість таких компонентів. Наш досвід показує, що навіть у ситуаціях, коли зміни ініціюються самими суб’єктами,  корисно подивитися на окремі їх частини і запитати себе: «Чи повинна ця конкретна частина змінитися?»

Повернемося  до класу Order  і на цей раз звернемо увагу на атрибут з client ID, custid. Ідентифікатори клієнтів змінювати не обов’язково: було б дивно, якби кошик з книгами, зібраними одним клієнтом, раптом перейшов у власність іншого. Сам факт існування такої функції створює потенційні проблеми з безпекою. Уявіть, що замовлення вже оплачений, але ще не відправлений. Якщо в цей момент зловмиснику вдасться змінити ідентифікатор клієнта, пов’язаний з цим замовленням, він фактично вкраде придбаний товар. Цього допускати не можна.

Щоб ефективно запобігти цим проблемам на етапі проектування, сутності можуть бути частково незмінними. Для цього переконайтеся, що ідентифікатор клієнта присвоєно лише один раз і після цього його не можна змінити. Лістинг 7.2 показує приклад, коли приватні кінцеві модифікатори використовуються в атрибуті  custid класу Order. Це змушує нас ініціалізувати  поле  custid в конструкторі і після цього не дозволяє його міняти. Кожного разу, коли  викликається метод getCustid  , він повертає те саме посилання, яке вказує на  об’єкт CustomerID. У цьому списку CustomerID є примітивом домену і має бути незмінним.

Якщо уважно придивитися до цього коду, стає очевидним,  що метод getCustid не  містить нічого цікавого і що його можна замінити прямим доступом до поля. Лістинг 7.3 показує, як це зробити безпечно. Посилання в полі даних не можна змінити, а  об’єкт CustomerID, на який він вказує, також незмінний. Якщо  метод processPayment  безпосередньо звертається до  поля order.custic, він не зможе зробити з ним нічого небезпечного.

Цікавою особливістю цього коду є те, що ми скористалися допомогою компілятора для забезпечення цілісності  поля даних custid. Будь-які спроби змінити цей атрибут будуть зупинені під час компіляції і не закінчаться під час виконання.

ПОРАДА. Якщо у вас є атрибути, які не повинні змінюватися, зробіть сутності частково незмінними, щоб уникнути порушення цілісності.

Ви вже знаєте, що поля даних можна захистити інкапсуляцією, як показано в розділі 6.3, або зробити їх частково незмінними. Тепер звернемося до менш очевидного аспекту сутностей: їх допустима поведінка може змінюватися в залежності від того, в якому стані вони знаходяться.

Об’єкти стану суб’єкта

Робота з суб’єктами ускладнюється тим, що деякі дії не допускаються в певних штатах. На рис. 7.1 Показані варіанти сімейного стану: самотній і одружений. Більшість з нас погодиться, що побачення, коли вони не одружені, є прийнятною поведінкою. Однак після весілля побачення слід припинити, якщо тільки це не чоловік. Звичайно, одружена людина завжди може розлучитися і знову стати неодруженим, в такому випадку у нього знову буде можливість ходити на побачення і одружуватися повторно / повторно одружуватися. Але шлюб неможливий, якщо людина вже перебуває в шлюбі. Точно так само самотня людина не може розлучитися.

Звичайно, це груба модель. Він не враховує багатоженство або випадки, коли навіть неодружені/неодружені люди не можуть ходити на побачення, наприклад, якщо вони заручені або перебувають у відданих стосунках. Проте це наочний приклад того, що не завжди є можливість виконати ті чи інші дії з сутностями.

Дотримання правил держави суб’єкта

Повертаючись до програмного забезпечення, воно повинно бути розроблено таким чином, щоб дотримуватися таких правил для організацій, інакше можуть виникнути проблеми з безпекою: наприклад, книгарня може відправити неоплачений товар. І це не така вже й рідкісна ситуація. Неповнота, некоректність або повна відсутність механізму управління державою суб’єкта господарювання можна знайти практично в будь-якій кодовій базі значного розміру.

Причина проблем безпеки полягає в тому, що часто не існує правил зміни держави, або вони неявні і розпливчасті. У багатьох випадках видно, що ніяких навмисних зусиль щодо дизайну не робилося, а правила виникали в кодовій базі поступово і, швидше за все, безсистемно.

У коді це часто виражається або у вигляді правил, вбудованих в методи корисності, або у вигляді умовних виразів в методах сутності. Лістинг 7.4 показує перший варіант. Існує таке правило: якщо начальник одружений, розмови, які він веде в позаробочий час, не повинні перетворюватися в знайомства. Цього правила дотримується  метод післяроботи  в класі Work,  а не  клас Person.

Якщо ви ще раз подивитеся на код, то помітите щось дивне. Неприпустимість знайомства в шлюбі – загальне правило. Однак у цій кодовій базі ми реалізували її так, ніби вона застосовується саме до  сценарію післяробіття. Це краще було б описати, як правило, про те, коли можна ходити на побачення. У нашому випадку логічніше було б помістити його всередину  методу дат класу Person.

У реальних проектах ми регулярно стикаємося з сутностями, які виглядають як звичайні структури (класи з закритими полями даних, сетери і геттери). У таких випадках необхідно дотримуватися правил поведінки суб’єкта господарювання на рівні методів обслуговування. Але в міру розвитку кодексу дотримання цих правил стає непослідовним. Деякі з них дотримуються не всіма компонентами системи. Крім того, це складно зрозуміти  які правила діють в тій чи іншій ситуації. Для цього потрібно проаналізувати всі фрагменти коду, в яких використовується сутність, і знайти умовні вирази, що запобігають порушення правил. Коротше кажучи, такий підхід не має практичних механізмів для відстеження того, які правила застосовуються до суб’єкта господарювання.

Труднощі аудиту йдуть рука об руку з труднощами тестування. Уявіть, що ви хочете написати модульний тест, який гарантує, що одружений менеджер не може ходити на побачення. Для цього потрібно протестувати умовний вираз в методі післяроботи, тобто створити макети будь-якої з його залежностей, наприклад підключення до бази даних. А також надати дані тестів,  Сюди входять  об’єкти начальника і співробітника  (останній – це підлеглий, з яким керівник може зустрітися). Нарешті, вам потрібно переконатися, що метод післяроботи  дійсно повідомляє про невідповідні відносини, тому вам потрібно створити макет структури журналювання та шукати  поганий рядок яйця в записах   журналу . І якщо в  іншому місці кодової бази є метод coffeeBreak , Вам доведеться зробити для нього те ж саме, щоб менеджер ні з ким не пішов на побачення під час невеликої перерви. Це не найпростіший спосіб перевірити правило «одружений бос не повинен ходити на побачення».

Було б краще, якби методи самого суб’єкта господарювання займалися дотриманням правил держави. Але навіть такий підхід може призвести до небезпечних невідповідностей. Лістинг 7.5 показує ще одну версію коду знайомств. Тут, перш ніж дозволити менеджеру піти  на побачення, клас Person  перевіряє його сімейний стан (зверніть увагу на  виписку if  у методі дати).

Такий підхід однозначно є кроком у правильному напрямку. Принаймні бізнес-правила тепер знаходяться всередині  класу Person. Запрошення на побачення може трапитися як поза роботою, так і під час невеликої перерви, але один і той же код завжди виконує перевірку. Це знижує ризик невідповідностей, які можуть привести до дірок у безпеці.

На жаль, державне управління все ще неявне, або, м’яко кажучи, заплутане. Ми часто бачимо, чи твердження  розкидані по коду глибоко всередині методів сутності. При аналізі історії змін коду зазвичай виявляється, що вони були додані один за одним для обробки якихось особливих випадків. Іноді сукупність всіх цих правил виявляється логічно суперечливою моделлю.

Очевидно, що реалізація стану у вигляді умовних виразів, поміщених в корисні методи або методи сутності, не є найкращим архітектурним рішенням. Але чи проблематично це з точки зору безпеки? Так. Той факт, що сутність дозволяє використовувати метод, який не повинен бути доступний у цьому стані, може служити відправною точкою для організації атаки, яка використовує цю помилку. Наприклад, якщо нові товари помилково дозволено додавати в замовлення, яке вже було оплачено, зловмисник може скористатися цим, щоб отримати безкоштовні товари.

Повернемося до книжкового інтернет-магазину. Перед подачею замовлення потрібно переконатися, що ви його оплатили. Лістинг 7.6 показує, як би виглядала ця логіка, якби вона була розміщена в методі processOrderShipment за межами  класу Order.

У  processOrderShipment є  одна невелика заява  if, яка запобігає відправці неоплачених товарів, і вона відноситься до якогось іншого класу. Легко уявити, що в міру додавання все більшої кількості коду ця перевірка буде пропущена або порушена. Якщо ви не переконаєтеся, що оплатили своє замовлення перед його відправкою, ваші клієнти отримають лазівку, яка дозволить їм не платити за замовлені товари. І якщо вони дізнаються про це,  Ви можете понести серйозні фінансові втрати, спостерігаючи за раптовим потоком неоплачених товарів, що відвантажуються зі складу. Якщо ви пропустите обробку стану суб’єкта господарювання, ви можете фактично створити діру в безпеці.

Азартні онлайн-ігри та легкі гроші

Гральні сайти часто влаштовують акції, пропонуючи відвідувачам бонусні бали або безкоштовні ігри. Багато хто намагається перетворити їх в реальні гроші, які можна було б вивести. Для цього вони беруть свої очки, трохи грають зі змінним успіхом, потім додають ще кілька очок в надії, що система рано чи пізно не зможе відрізнити реальні гроші від бонусів. Таким чином багато гральних сайтів зазнали реальних фінансових втрат.

Найкращим способом боротьби з такими атаками є сайти, де ці ситуації були спеціально змодельовані у вигляді набору сутностей, які логічно і плавно охоплюють відповідні правила. Чи можу я використовувати бонусні бали в грі? Чи можна їх вивести, і якщо так, то в якій кількості? Бонусні бали – це не просто грошова сума, а складне утворення з безліччю станів.

Реалізація стану суб’єкта господарювання як окремого об’єкта

Ми рекомендуємо вам спроектувати та реалізувати стан сутності як окремий клас. Це дозволяє використовувати об’єкт стану як об’єкт-помічник делегованої сутності. Будь-який доступ до сутності спочатку перевіряється за допомогою цього об’єкта.

Повернемося до  прикладу сімейного стану. Лістинг 7.7 показує, як виглядає об’єкт помічника MaritalStatus. Він містить правила, пов’язані з сімейним станом, але не більше того. Наприклад, в методі дат  виклик  рамок Validate  допомагає дотримуватися правила про неприпустимість знайомства для одруженого чоловіка.

Ці допоміжні об’єкти мають лаконічний код. Приклад сімейного стану – це практично найпростіша з можливих діаграм стану. Це стає трохи складніше, якщо ви забезпечуєте підтримку живих/мертвих станів, додаючи приватну булеву змінну живу, що спочатку вірно. Коли людина вмирає, вона встановлюється  на фальшивість,   а після цього значення подружнього поля  втрачає будь-який сенс.

Наявність логіки «живий/мертвий»  у самій сутності, ймовірно, призведе до появи кількох тверджень, які зроблять код менш читабельним та перевіреним та призведуть до ослаблення безпеки з часом. Крім того, та ж логіка може бути додана  в клас помічників MaritalStatus, і в цьому випадку код все одно буде легко підтримувати. Прямим наслідком стислості допоміжного об’єкта є простота тестування. У Code Listing 7.8 показує кілька можливих тестів, які перевіряють відповідність правил об’єктом MaritalStatus.

Зверніть увагу, як легко прочитати цей код. Тестовий should_ allow_dating_after_divorces явно говорить про те, що в  разі одруження і подальшого розлучення можна знову ходити на побачення. Цьому сприяє те, що класи названі відповідно до понять, які існують в предметній області, таким як «сімейний стан» – MaritalStatus.

Тепер давайте подивимося, як це представництво держави можна вплести в сутність. У Лістингу 7.9 ми дозволяємо сутності  Особа перевіряти  об’єкт помічника MaritalStatus на початку кожного публічного методу, щоб визначити, чи є виклик дійсним.

Перенесення управління станом на окремий об’єкт робить код сутності набагато надійнішим і стійким до незначних порушень цілісності бізнесу, наприклад, коли клієнт уникає оплати замовлень перед їх відправкою. Окремі об’єкти станів рекомендуються, коли у вас є як мінімум два стану з різними правилами і переходи між ними не зовсім тривіальні. Ми, ймовірно, не стали б використовувати цей підхід для представлення стану лампочки (включення/виключення, перехід завжди можливий і змінює стан). Але для будь-яких більш складних випадків рекомендуємо використовувати окремий об’єкт стану.

Як уже згадувалося, представлення сутності як змінюваного об’єкта добре працює в однопотокових середовищах. Але якщо потоків багато, вам доведеться активно використовувати синхронізоване ключове слово у вашому коді, щоб вони не зіпсували ваш стан. На жаль, це викликає інші проблеми, такі як обмеження пропускної здатності та потенційне тупикове блокування. Далі давайте розглянемо ще один підхід до проектування, який добре працює в багатопотокових середовищах.

Знімки сутностей

Перейдемо до багатопотокових середовищ, в яких екземпляр сутності може бути доступний декільком потокам одночасно. У високопродуктивному рішенні, де час відгуку відіграє ключову роль, слід уникати запитів до бази даних. Зазвичай це стосується торгівлі цінними паперами, потокової передачі, багатокористувацьких ігрових додатків та високоадаптивних веб-сайтів. У таких ситуаціях час, необхідний для отримання інформації з бази даних, унеможливить повернення швидких відповідей. Замість цього сутності повинні зберігатися в пам’яті якомога частіше, використовуючи, наприклад, загальний кеш, такий як Memcached. Всі потоки, яким потрібно працювати з сутністю, беруть дані з кешу, який розділяє представлення сутності по потоках. Це мінімізує час відгуку і збільшує ємність, але вимагає додаткових зусиль при проектуванні сутностей: вони повинні вести себе належним чином в багатопотоковому середовищі.

Щоб вирішити цю проблему, ви можете додати багато синхронізованих ключових слів до свого коду, але це призведе до того, що багатьом потокам доведеться чекати один одного, і різко знизить пропускну здатність системи. Що ще гірше, це може призвести до  глухого кута, коли два потоки чекають один одного нескінченно. Але ми можемо вирішити цю проблему за допомогою іншої схеми проектування,  шляхом представлення сутностей у вигляді знімків.

Сутності, представлені незмінними об’єктами

Сутність не обов’язково повинна бути представлена в коді як змінюваний клас. Замість цього ви можете створити його знімки, які дозволяють переглядати його і виконувати над ним дії. Найпростіше описати його метафорою.

Уявіть, що у вас є старий друг, якого ви давно не бачили. Ви живете в різних містах, але підтримуєте зв’язок із сайтом обміну фотографіями. Ви регулярно переглядаєте фотографії, зроблені другом, і стежите за змінами в його житті. Він може змінити зачіску або переїхати в новий будинок, і він, природно, дорослішає або старіє, як будь-яка людина. Незважаючи на зміни всіх цих атрибутів, особистість вашого друга залишається колишньою. Ви спостерігаєте за різними подіями в його житті і підтримуєте зв’язок, але ніколи не зустрічаєтеся особисто (рис. 7.2).

А як щодо фотографій? Чи можна їх назвати вашим другом? Звичайно, ні. Це всього лише фотографії вашого друга. Кожна фотографія зображує його в певний момент часу. Фотографію можна скопіювати і викинути, і вам все одно, копія це чи оригінал. Ваш друг все ще живе своїм життям у далекому місті.

Знімки сутностей працюють  аналогічним чином. В інтернет-магазині замовлення створюються, коли клієнти щось купують. Кожне замовлення має значний термін служби і змінюється, коли клієнт додає до нього товари, вибирає спосіб оплати тощо. Теоретично стан може зберігатися в базі даних. Але коли код хоче його отримати, ви робите його знімок і форматуєте як  клас OrderSnapshot  є незмінним поданням відповідної сутності. Це показано в лістингу 7.10. Цей код надає знімок того, як виглядає замовлення, коли ви його запитуєте.

Навіть якщо сутність порядку все ще існує на концептуальному рівні, вона не виражається як змінюваний  клас в коді. Замість цього, клас OrderSnapshot  надає необхідну інформацію про сутність, щоб ви могли візуалізувати її в графічному інтерфейсі інтернет-магазину, наприклад. Ідея концепції знімка полягає в тому, що служба домену переходить до бази даних,  Беручи з собою фотоапарат, і повертається назад з фотографією, яка фіксує замовлення в певний момент часу. OrderSnapshot – це не просто тривіальний об’єкт звітності: як і класична сутність, він містить цікаву логіку домену. Наприклад, він здатний розрахувати загальну кількість товарів у замовленні та переконатися, що воно відповідає асортименту, дозволеному до відправки.

Але як щодо самої сутності? Чи може вона існувати без змінного класу? Можливо, але на концептуальному рівні – так само, як ваш друг з іншого міста, якого ви ніколи не бачили наживо. Ви не взаємодієте з сутністю безпосередньо, а лише отримуєте знімки її стану. Єдине місце, де порядок представлений у вигляді змінного стану, знаходиться в базі даних:  мова йде про рядок  в таблиці Orders  і відповідних рядках в  таблиці OrderLines.

Зміни стану вихідного суб’єкта

Ми показали, як змінювана сутність може бути представлена за допомогою незмінних знімків. Але якщо це реальна сутність, вона повинна мати можливість змінювати свій стан. Як цього можна досягти, якщо його представництво незмінне?

Нам потрібен якийсь механізм модифікації сутності (тобто її внутрішніх даних). Для цього можна надати послугу домену, на яку ви будете відправляти оновлення. Лістинг 7.11 показує, що служба доменів OrderService має новий метод addOrderItem, який відповідає за оновлення сутності.

Метод addOrderItem   перевіряє умови, щоб переконатися, що зміна дійсна, і оновлює внутрішні дані за допомогою команд SQL, що надсилаються безпосередньо до бази даних, або використовуваної структури зберігання даних. Такий підхід забезпечує високу доступність, оскільки ви уникаєте блокування тільки для читання, яке нібито відбувається набагато частіше, ніж запис (тобто зміна даних). Операції запису, які можуть вимагати блокування, відокремлюються від читання, що дозволяє уникнути проблем безпеки, пов’язаних з недоступністю даних.

Недоліком такого підходу є те, що він суперечить деяким принципам орієнтації об’єкта, особливо рекомендації розміщувати дані і пов’язану з ними поведінку якомога ближче один до одного, бажано в одному класі. Тут ми розділили сутність на частини, розмістивши операції читання в об’єкті значень одного класу, а операції запису в службі доменів іншого класу. Архітектура часто вимагає компромісів. У таких випадках висока доступність може бути настільки важливою, що варто пожертвувати, наприклад, певними принципами об’єктно-орієнтованого програмування.

Ця ідея не така дивна, як можна подумати. Аналогічний підхід використовується і в інших ситуаціях, іноді в дещо іншій формі. Шаблон проектування сегрегації відповідальності за командний запит (CQRS) і принцип одного письменника, запропоновані Грегом Янгом і Мартіном Томпсоном відповідно, мають схожі характеристики.

Знімки сутностей підтримують не тільки доступність, але і цілісність. Оскільки знімок незмінний, стан виду не може бути некоректним в принципі. Нормальна сутність з методами, які змінюють її стан, схильна до такого роду помилок, але знімки – ні. Звичайно, існує код для зміни внутрішніх даних, який також може мати дефекти, але принаймні знімок, який використовується для представлення стану сутності, не може бути змінений.

Коли використовувати знімки

Концепція сутності, яка не має власного класу, безсумнівно, суперечить стандартним методам об’єктно-орієнтованого програмування і може здатися неінтуїтивно зрозумілою. Для тих, хто мислить категоріями ООП, розміщення визначення даних суб’єкта господарювання в одному місці, а механізми оновлення тих самих даних в іншому має бути незручним. Однак існують ситуації з жорсткими вимогами до потужності та доступності (будь то веб-сайти з високим навантаженням або лікарня, персонал якої повинен мати миттєвий доступ до медичних записів пацієнтів), в яких, на нашу думку, варто розглянути знімки сутностей.

Коли справа доходить до безпеки, ще одна цікава перевага використання знімків з’являється в ситуаціях, коли для читання та зміни даних потрібні різні привілеї. Якщо потрібно лише відобразити стан даних (наприклад, коли користувач переглядає кошик), достатньо буде методу, який їх отримує. Аналогічним чином вам знадобиться відповідний метод для зміни даних (наприклад, при додаванні товару в кошик). У класичному ООП сутність має методи підтримки як читання, так і письма. Однак під час відображення даних клієнтський код також має доступ до методів їх запису. Існує потреба в якомусь іншому механізмі безпеки, який контролює зміни, внесені до суб’єкта господарювання, і пригнічує їх у ситуаціях, коли потрібне лише читання.

Коли ви використовуєте знімки сутностей, клієнтський код, який повинен прочитати щось, має доступ лише до незмінного знімка і не може викликати методи зміни. Лише клієнти, яким дозволено вносити зміни, зможуть отримати доступ до служби доменів, призначеної для оновлення сутності. Таким чином, цей шаблон проектування також забезпечує цілісність даних у деяких поширених сценаріях. Тому в середовищі, в якому різні потоки отримують доступ до однієї і тієї ж сутності (можливо, навіть одночасно), знімки є ефективним способом забезпечення високої доступності та цілісності.

До теперішнього часу ми досліджували дві архітектури, одна з яких добре працює в однопотокових середовищах, а інша – в багатопотокових середовищах. Однак рівень складності може збільшуватися з іншої причини: коли сутність має багато різних станів. Може бути незручно працювати більш ніж з десятьма станами, але часто можна застосувати альтернативний підхід – представляти складні стани у вигляді ланцюжка сутностей.

Ретрансляція сутностей

Багато утворень мають невелику кількість окремих станів, які досить легко зрозуміти. Наприклад, сімейний стан в одному з попередніх прикладів має всього кілька станів і переходів між ними (рис. 7.3).

 

Діаграму стану такого розміру зрозуміти нескладно. Це також було б легко реалізувати, використовуючи окремий державний об’єкт, розглянутий у розділі 7.2.

Іноді діаграма стану сутності стає настільки великою, що зрозуміти її не так-то просто. Можливо, він був розроблений таким чином з самого початку, але найчастіше це результат довгої історії змін. Коли зміни вперше вносилися, вони, швидше за все, вважалися незначними виправленнями, але з часом вони накопичилися і привели до появи багатьох станів. Цілком можна уявити, що спочатку замовлення в книжковому інтернет-магазині мали два стани:  «отримав»  і «відправив». Але через деякий час їх діаграма могла прийняти такий вигляд (рис. 7.4).

Буває складно зрозуміти всі можливі стани і переходи такої сутності, навіть якщо у вас перед очима їх схема. Імплементація цих станів в код з упевненістю, що кожна з них дотримується різних правил, буде кошмаром. Якщо така сутність є окремим класом, її код стає настільки складним, що в нього можуть закрадатися невідповідності, які важко виявити. Якщо діаграма стану вашого замовлення аналогічна тій, що показана на рис. 7.4, ви повинні щось робити. Потрібно розбити його на частини. П’ятьма станами можна управляти. Десять штатів все ще терпимі. Але жонглювати 15 штатами просто занадто ризиковано. І саме в таких випадках ми рекомендуємо використовувати ланцюжок сутностей.

Основна ідея  ретрансляції сутності полягає в розбивці життєвого циклу сутності на фази, кожна з яких повинна представляти різну сутність. Коли фаза завершена, сутність  зникає і її місце займає інша, як в естафеті. Поглянемо, наприклад, на малюнок 7.5, де життя людини ілюструється двома способами: по-перше, як єдине ціле, яке проходить через безліч станів,  А потім у вигляді ланцюжка сутностей.

У лівій частині рис. 7.5 Ми бачимо життя людини, представлену у вигляді єдиної сутності: людина народжується і залишається дитиною протягом декількох років; потім починається період підліткового віку, після якого людина стає дорослою людиною з усіма витікаючими наслідками; Потім йдуть старіння і смерть. Людину можна вважати одним і тим же сутністю, який проходить через такі стани: народження, дитинство, юність, зрілість, старість і смерть.

У правій частині зображення показано таке ж життя, але у вигляді ланцюжка сутностей. Народжується і росте дитина. Одного разу він зникає, а його місце займає молода людина, що приймає естафету. Через кілька років замість юнака з’являється дорослий, який рано чи пізно поступається місцем старому, і той в підсумку вмирає. Цю ситуацію можна вважати чергуванням різних персонажів.

Ми показали два різних погляди на одне й те саме. Обидві ці ідеї однаково вірні, вони звертають увагу на різні аспекти і підходять для різних ситуацій. Іноді корисно переключити фокус з одного підходу на інший, наприклад, коли у вас є сутність з кількома станами. Як показує наш досвід, такий перехід може слугувати ефективним способом роботи з суб’єктами, держави яких вийшли з-під контролю. Розрослася діаграма станів розбивається на фази, кожна з яких моделюється як окрема сутність, потім отримані сутності утворюють релейний ланцюг. Кожна сутність в ланцюжку управляє декількома станами, що дозволяє знизити рівень складності і уникнути зайвих ризиків.

Ефективність реле сутності пояснюється можливістю поділу загального життєвого циклу сутності на фази, які моделюються послідовно. Щоб це працювало справно, не можна допускати повернення до вже пройденої фазі. Ця ідея може бути застосована навіть в тому випадку, якщо віддача відбувається, але при цьому втрачається простота і bo’.Більшість переваг, які дає ця абстракція. Далі ми повернемося до страшної діаграми статусу замовлення в книжковому магазині та покажемо вам, як перетворити його на більш зрозумілий ланцюжок сутностей.

Розбиття діаграми станів на фази

Ще раз розглянемо надмірно складну сутність, показану на рис. 7.4, і спробуємо змоделювати його у вигляді естафети сутностей. Шукайте місця, де одна група держав перетворюється в іншу. Бажано, щоб у вас не було можливості повернутися в першу групу після того, як ви її покинули. На рис. На малюнку 7.6 представлений один з варіантів поділу станів.

Як бачите, складна схема онлайн-замовлення може складатися з двох етапів: до і після оплати. Назвемо  першу фазу попереднім замовленням, а другу заключною. Ці етапи повинні бути розроблені як окремі сутності. У нашій естафеті беруть участь два бігуни: перший починає забіг (попереднє замовлення), а другий, прийнявши естафету від першого, фінішує (фінальне замовлення). Остаточне замовлення  – це результат оплати заздалегідь.

Якщо ще раз поглянути на діаграму стану, показану на рис. 7.6, можна побачити ще одне місце, яке відповідає цим вимогам – коли одержувач відмовляється від доставленої посилки. Різниця полягає в тому, що не всі доставлені замовлення призводять до відхилення, а лише деякі. Однак це гарне місце, щоб розбити діаграму стану. Пам’ятайте, що відхилене замовлення з’являється не автоматично, а тільки після того, як одержувач відмовиться від нього.

В результаті перетворень отримуємо більш просту конфігурацію. Як показано на рис. 7.7, у нас більше немає єдиного цілого з величезною кількістю держав. Замість цього ми маємо справу з трьома досить простими сутностями.

Кожна з трьох сутностей (попереднє замовлення, остаточне замовлення і відхилене замовлення) має чотири або п’ять можливих станів, тому реалізувати їх окремо буде досить просто. Використовуючи об’єктну модель попереднього стану, ви зможете безпечно реалізувати всі три сутності.

Коротко зупинимося на переходах – передачі естафети між сутностями в ланцюжку. Коли попереднє замовлення розміщено та оплачено, воно переходить у свій остаточний «оплачений» стан. У цей момент, в його початковому стані «оплачений», виникає остаточне замовлення. Естафета переходить від попереднього замовлення, яке досягло своєї мети, до остаточного, який тільки починає свою гонку. Подібна ситуація виникає, коли сутність «молодої людини» переходить в стан «вже не молода людина», в результаті чого виникає сутність «дорослої людини». І так само, як дорослі не можуть повернутися в підлітковий вік, остаточний порядок не може бути переведений в стан до початкового «оплаченого».

Перехід від кінцевого замовлення (оплаченого і відправленого покупцеві) до відхиленого точно такий же, але з однією невеликою відмінністю. Коли остаточне замовлення досягає свого остаточного стану «доставлено», це не завжди призводить до наступного об’єкта. Відхилене замовлення можливо тільки в тому випадку, якщо остаточне замовлення було відмовлено при доставці (наприклад, ніхто не хотів підписуватися за ним або не зміг знайти вказану адресу). Але в більшості випадків доставка остаточного замовлення не призводить до його відхилення.

Таке архітектурне рішення значно спрощує написання коду, роблячи його більш стійким до помилок. Якщо діаграма стану велика і складна, як показано на рис. 7.4, дуже складно переконатися у відсутності незвичайних, прихованих шляхів виконання, які призводять до порушення правил ведення бізнесу. Але, маючи три більш простих сутності з чотирма або п’ятьма станами для кожного, як на рис. 7.7, ви можете легко переконатися, що код веде себе так, як треба. Ви різко знижуєте ризик вразливостей. Тепер повернемося в теоретичну площину і подумаємо, коли цей підхід слід застосовувати, а коли ні.

Коли варто формувати естафету суб’єктів

Щоб такий підхід був корисним, повинні бути дотримані три умови:

  • У сущности занадто багато состояний;

  • Неможливість повернення в одну з попередніх фаз;

  • Арості переходи від однієї фази до іншої з невеликою кількістю направлених (бажано з одним).

Якщо кількість станів у вашому суб’єкті знаходиться в розумних межах, немає сенсу підвищувати рівень складності шляхом створення нових сутностей з іншими назвами. Якщо у вас менше десяти станів, ми не рекомендуємо формувати ланцюжок з сутності. Аналогічно, не рекомендується розділяти сутність на дві частини, якщо ви можете повернутися з більш пізньої фази на більш ранню. Перевага ланцюжка сутностей пов’язана з тим, що після того, як ви закінчите працювати з організацією, ви ніколи не повернетеся до неї. У контексті нашого прикладу із замовленнями це відбувається, коли попереднє замовлення (все ще на стадії оформлення замовлення) оплачено. В результаті виникає остаточне замовлення, яке не можна зробити попереднім знову.

Можливість повернення до однієї з попередніх сутностей така ж, як якщо б один бігун передав естафету тому, хто вже біг до нього. Ви втрачаєте простий порядок передачі естафети від одного бігуна до іншого до кінця забігу. Замість цього ви отримуєте свого роду спрямований граф, де ви можете реконструювати сутності з інших фаз. Зникає вся простота реле.

Якщо перехід від одного суб’єкта до іншого може йти різними шляхами, слід подумати, чи переваги такого підходу переважають витрати на його реалізацію. Якщо сутність може виникнути лише в певний момент, це дає нам певну простоту. Якщо таких пунктів кілька, подумайте про зміну моделі. Можливо, вдасться забезпечити остаточний стан для фази? Якщо немає, то, можливо, дві фази варто об’єднати в одну.

Нарешті, можна отримати величезну діаграму стану, переходи в якій нагадують хитромудрий клубок пряжі. Навіть не намагайтеся спростити його за допомогою реле сутності, всі конструкторські зусилля виявляться марними. Плутанина не зникне, навіть якщо витягнути кілька ниток. Натомість рекомендуємо переробити модель. Зустріньтеся з профільними експертами та обговоріть наступні питання:

  • В чому настояща причина того, що модель вийшла складною?

  • Дійсно чи виправдана вся ця складність?

  • Лежать чи в основі кожного складного архітектурного рішення реальні вимоги до бізнесу?

  • Виправдовують чи ці бізнес-потреби витрати, викликані такою складністю та, можливо, недостатньою захищеністю системи?

У цьому розділі розглядається кілька способів спрощення архітектури. У табл. На рисунку 7.1 наведено короткий перелік основних напрямків кожного підходу та питань безпеки, на які він розрахований.

Перше архітектурне рішення, яке ми розглянули, полягало в тому, щоб мінімізувати кількість складових елементів. Для цього сутність роблять максимально частково незмінною. Наступний підхід, об’єкт станів, спрямований на інкапсуляцію переходів між станами однієї сутності і добре працює в однопотокових середовищах. Якщо потоків багато, має сенс представляти сутність у вигляді знімків для запобігання конфліктів. Нарешті, якщо сутність є складною і включає багато станів, як альтернативний підхід, вона може бути змодельована як ретрансляція сутності, що зменшує кількість станів, які доводиться розглядати одночасно.

Резюме

  • Сущності можна спроектувати так, щоб вони були частково незмінними.

  • Код для роботи зі станами легше тестувати і розробляти, якщо винести його в окремий об’єкт.

  • Багатопоточні середовища високої ємності вимагають ретельного проектування.

  • Механізми блокування в базі даних можуть обмежити доступність сутностей.

  • Снимки власності дозволяють відновити високу доступність у багатопоточних середовищах.

  • Естафета сутностей (когда задоволення умов однієї сутності породжує інших) — це альтернативний підхід до моделювання сутності з безліччю состояний.

Ми використовували матеріали з книги “Безпека by design”, які  написали Дэн Берг Джонсон, Дэниел Деоган, Дэниел Савано.

Інші статті по темі
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 1. (Роль проектування у безпеці)
Проектування відіграє надзвичайно важливу роль у гарантуванні безпеки в різних сферах, від технологій до інфраструктури та бізнесу. Цей процес є фундаментальною складовою для створення рішень, які ефективно запобігають загрозам, забезпечують конфіденційність та зберігають цінності. Розглянемо ключові аспекти ролі проектування у забезпеченні безпеки.
689
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 2. (Антракт: анти-“Гамлет”)
У цій статті описується роль глибокого моделювання у забезпеченні безпеки інформаційних систем та бізнес-цілісності. Розглядаються ризики поверхового моделювання, які можуть призвести до недостатнього рівня захисту та дефективної безпеки.
482
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 3. (Основні концепції предметно-орієнтованого проектування)
Основні Концепції Предметно-Орієнтованого Проектування полягає в поясненні важливих концепцій цього підходу, включаючи абстракцію, глибокий аналіз предметної області та покращення процесів розробки. Виокремлені аспекти покращення якості та продуктивності роботи, що досягаються завдяки використанню предметно-орієнтованого проектування.
602
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 4. (Концепції програмування, що сприяють безпеки)
Застосування цих концепцій у програмуванні сприяє підвищенню рівня інформаційної безпеки. Вони допомагають уникнути загроз та вразливостей, забезпечуючи захист даних та надійність програмних продуктів.
495
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 5. (Доменні притиви)
У цій частині. Як доменні примітиви допомагають писати безпечний код. Боротьба з витоком даних за допомогою об'єктів одноразового читання. Поліпшення сутностей з допомогою доменних примітивів. Ідеї, запозичені із аналізу помічених даних.
485
ОсвітаСамонавчання
Читати далі
Безпека by design. Частина 8. (Роль процесу доставки коду у безпеці)
У цій частині. Модульні випробування, спрямовані на безпеку. Перемикачі функціональності з погляду безпеки. Написання автоматизованих тестів безпеки. Чому важливими є тести доступності. Як неправильна конфігурація призводить до проблем безпеки.
525
Знайшли помилку?
Якщо ви знайшли помилку, зробіть скріншот і надішліть його боту.