Статьи

Модель предметной области как REST Anti-pattern

Сегодня JavaLobby опубликовал еще одну статью о модели домена как REST , используя Spring и Jersey. Как уже указывалось, это действительно анти-шаблон, и он не является RESTful, и не может быть таковым.

Пункт, который пропускают все эти типы статей, — это HATEOAS , гиперссылка на ресурсы. Если вы раскрываете модель своего домена, в основном говоря: «Вот все, что я получил, используйте по своему усмотрению», то нет разумного способа создания ссылок между ресурсами, которые отображают состояние приложения. Нет разумного способа сказать клиенту «вот что вы можете сделать дальше», потому что API «REST» позволяет в любое время что-нибудь сделать.

Вот пример из моего собственного приложения, который показал мне проблему с этим подходом. У меня есть пользователи в моей системе. Мне нужно два способа работы со своими паролями: пользователю должно быть разрешено изменять свой пароль, а администратору должно быть разрешено сбрасывать пароль любого пользователя. В начале, когда я демонстрировал свою модель домена, URL-адреса для этого были следующими:

/user/<username>/changepassword/user/<username>/resetpassword

Я просто делаю то, что предлагает статья, то есть раскрыть мою модель предметной области и все, что я могу с ней сделать. В чем проблема с этим? Первая и наиболее очевидная проблема заключается в том, что очень трудно определить, кому разрешено делать то, что здесь. Мне нужно иметь проверки авторизации как «changepassword», так и «resetpassword». Первый должен проверить, является ли это тот же пользователь, который обращается к нему, а второй должен проверить, есть ли у пользователя, выполняющего доступ, роль администратора. Кроме того, поскольку клиент ДОЛЖЕН получить доступ к этим ресурсам, перейдя по ссылкам в гипермедиа (это ограничение, помните?), Наиболее очевидная вещь, которую нужно сделать, — перечислить их при доступе к / user / <username> /. Но тогда мои списки ссылок также должны выполнять эти проверки авторизации, потому что, если пользователь не является администратором, тогда «пароль для сброса»ссылка не должна быть там. Я не должен позволять клиентам видеть ссылки, по которым они не могут разумно перейти! Мой пользовательский интерфейс также будет довольно сложным, потому что на одном экране я мог бы выполнить «сброс пароля» и ряд других административных задач, каждая из которых использует разные части модели предметной области, и, следовательно, его подверженность API, и, следовательно, его хрупкость если API меняется, огромен. Это просто очень плохая ситуация, в которую вы попадете, следуя указаниям во всех этих статьях, посвященных модели вашего домена.и поэтому его подверженность API, и, следовательно, его хрупкость, если API изменяется, огромна. Это просто очень плохая ситуация, в которую вы попадете, следуя указаниям во всех этих статьях, посвященных модели вашего домена.и поэтому его подверженность API, и, следовательно, его хрупкость, если API изменяется, огромна. Это просто очень плохая ситуация, в которую вы попадете, следуя указаниям во всех этих статьях, посвященных модели вашего домена.

Так что же делать вместо этого? Хитрость заключается в том, чтобы вместо этого раскрыть варианты использования. Это так просто. Теперь главная проблема в том, что вы должны знать, каковы ваши варианты использования! И, вероятно, именно поэтому все эти статьи делают анти-паттерн доменной модели: из-за их упрощенной природы они думали только о том, «у нас должны быть пользователи в нашей системе», а не делали следующий шаг », что мы можем сделать с ними? «, потому что тогда вам нужно гораздо больше решать, что делает ваша система. Поскольку статьи должны быть разумно сосредоточены на одной вещи, они просто не идут туда. Но для Вас, если вы сделаете это, вы получите беспорядок, описанный выше.

Что мы сделали в нашем REST API, чтобы исправить вышесказанное? Мы просто посмотрели на наши примеры использования и соответствующим образом реорганизовали REST API. Для вышесказанного они относятся к двум различным вариантам использования, которые являются обработкой учетной записи и администрированием пользователей. И поэтому мы изменили API на что-то вроде этого:

/account/changepassword/administration/users/<username>/resetpassword

Если клиент перейдет в / account /, что он может сделать, сначала перейдя в «/» и найдя эту ссылку, он получит список ссылок с тем, что вы можете сделать в своей учетной записи, например, «changepassword». Сделайте GET по этой ссылке, и клиент получит форму с двумя полями: старый пароль и новый пароль. Клиент может показать это как три поля, с дублирующим новым полем пароля, чтобы гарантировать, что пользователь набрал его правильно. Затем клиент может POST «изменить пароль», чтобы внести фактические изменения. Мне не нужно делать никаких проверок авторизации, так как клиент неявно обращается к своей учетной записи, поэтому нет никакого способа испортить это, даже умышленно. Для стороны администратора клиент просматривает / Administration / users / (и снова эта ссылка была получена из «/», если пользователь является администратором),перечисляет / ищет пользователей, получает ссылку на конкретного пользователя, выполняет GET для «resetpassword», чтобы получить форму для него, а затем заполняет ее и отправляет ее, чтобы внести изменения. REST API всегда говорил клиенту, что ему разрешено делать, используя гипертекст для управления состоянием приложения. Это то, что HATEOAS означает на практике, и это ОЧЕНЬ полезно, если вы выставляете свои варианты использования, и ОЧЕНЬ раздражает, если вы выставляете свою модель домена (просто потому, что вы не можете). Этот подход также устраняет проблему, изложенную в статье с циклическими ссылками, просто потому, что в случаях использования нет циклических ссылок.REST API всегда говорил клиенту, что ему разрешено делать, используя гипертекст для управления состоянием приложения. Это то, что HATEOAS означает на практике, и это ОЧЕНЬ полезно, если вы выставляете свои варианты использования, и ОЧЕНЬ раздражает, если вы выставляете свою модель домена (просто потому, что вы не можете). Этот подход также устраняет проблему, изложенную в статье с циклическими ссылками, просто потому, что в случаях использования нет циклических ссылок.REST API всегда говорил клиенту, что ему разрешено делать, используя гипертекст для управления состоянием приложения. Это то, что HATEOAS означает на практике, и это ОЧЕНЬ полезно, если вы выставляете свои варианты использования, и ОЧЕНЬ раздражает, если вы выставляете свою модель домена (просто потому, что вы не можете). Этот подход также устраняет проблему, изложенную в статье с циклическими ссылками, просто потому, что в случаях использования нет циклических ссылок.

Еще одним следствием этого является то, что ссылки в REST API почти всегда будут относительными, поскольку все, что они делают, это направляют клиента дальше в сценарий использования или дополнительный сценарий использования. Если вы выставляете доменные модели, у вас должны быть абсолютные ссылки, и они будут появляться здесь и везде, раскрывая внутренние ассоциации между сущностями. Это глупо до невероятности, но это то, что почти все из текущих статей о средах RESTful говорят вам. И я говорю: не надо! Это принесет вам только страдания.

С точки зрения безопасности с этим также легче работать. Теперь все, что мне нужно сделать, это добавить проверку «/ Administration /» для роли администратора, и после этого пользователь может сделать все, что ниже этого пункта. Не нужно дублировать эту проверку везде (и не нужно использовать аспекты, чтобы обойти это раздражение)!

Теперь, когда вы знаете, что делать, может возникнуть следующий вопрос: как это сделать? Проблема здесь в том, что поскольку все текущие «REST» -структуры не продумали HATEOAS, они на самом деле не позволяют вам создавать REST API для ваших приложений, и поэтому они не будут очень просты в использовании. Вы должны работать против них. Это касается и Джерси, и его друзей, так как они не допускают такой подход к анализу URL. В моем проекте Streamflow мы создали нашу собственную оболочку поверх Restlet . Restlet предоставляет отличную базу для приложений REST, но он слишком низкоуровневый, чтобы иметь дело с кодом приложения. Поместив поверх него тонкую оболочку, которая понимает понятие прецедентов и ссылок, мы смогли реально сократить объем кода, необходимый для всего этого.

Вот пример кода для «изменения пароля». Код использует технику маршрутизации, поэтому, чтобы получить / account / changepassword, фреймворк сначала берет «/» и находит для этого ресурс. Затем этот ресурс знает, как добраться до «/ account /», который, в свою очередь, знает, как добраться до «changepassword». Код выглядит так:

public class RootResource      extends CommandQueryResource{   @SubResource   public void account()   {      subResource( AccountResource.class );   }...}

Когда клиент нажимает «/», фреймворк автоматически просматривает этот класс и представляет для него гипермедиа (JSON или HTML, но Atom Services также является опцией). Клиент нажимает «/ account /», что приводит к этому:

 

public class AccountResource   extends CommandQueryResource{   public AccountResource( )   {      super( AccountContext.class );   }   @SubResource   public void profile()   {      subResourceContexts( ProfileContext.class, ContactableContext.class );   }}

Чтобы узнать, что клиент может сделать на этом уровне, вы смотрите в AccountContext. Если вы хотите продолжить работу с профилем учетной записи (настройка электронной почты, телефона и т. Д.), Клиент перейдет по ссылке «/ account / profile /», которая отображается. В нашем случае давайте посмотрим на AccountContext:

 

public class AccountContext{   public void changepassword( ChangePasswordCommand newPassword )         throws WrongPasswordException   {      UserAuthentication user = RoleMap.role( UserAuthentication.class );      user.changePassword( newPassword.oldPassword().get(), newPassword            .newPassword().get() );   }}

Он раскрывает одно взаимодействие на этом уровне сценария использования — / account / changepassword. Параметр сообщает фреймворку, что ему требуется в качестве входных данных, поэтому, если клиент выполняет GET, он может посмотреть на объект значения ChangePasswordCommand и представить его в виде формы. Если клиент выполняет POST, платформа анализирует входные данные в объекте значения и вызывает метод, позволяя ему «делать свое дело». В этом случае он ищет роль UserAuthentication, которая представляет собой миксин Qi4j, который реализует наш пользовательский объект. Этот объект был автоматически зарегистрирован фильтром аутентификации, поэтому мне не нужно его искать. В случае использования администратором код получит доступ к части «<username>» URL, чтобы он мог найти пользователя в хранилище.

И это в значительной степени так. Не только с этим проще работать для клиентов (которые просто переходят по ссылкам в гипермедиа), но и код в клиенте и приложении также абсолютно тривиален. Я также могу узнать, как выглядит мой REST API, начав с RootResource, и изучить API, нажав на классы, на которые он ссылается. Поскольку API является настолько детерминированным, учитывая эти классы, я также могу автоматически генерировать документацию, которая в основном гласит: «Учитывая ваш уровень авторизации пользователя, здесь представлены все ресурсы, к которым вы можете получить доступ через API». В частности, когда ваш API становится управляемым гипермедиа, как это, то, что вы хотите представить в качестве документации, это набор атрибутов «rel», которые клиент должен знать, в данном случае «account» и «changepassword». Что за URL?Похоже, это не бизнес клиентов! Он должен просто перейти к «/» и следовать ссылкам на основе атрибута «rel» этих ссылок. Тогда вы можете сказать, что ваш API «RESTful». Пока вы этого не сделаете, нет, у вас может быть «веб-API» или «HTTP API», но это не REST.

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

С http://www.jroller.com/rickard/entry/the_domain_model_as_rest