Транзакции в REST?


147

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

Прочитайте или обновите несколько ресурсов в рамках одной транзакции. Например, переведите 100 долларов с банковского счета Боба на счет Джона.

Насколько я могу судить, единственный способ реализовать это - обман. Вы можете отправить POST к ресурсу, связанному с Джоном или Бобом, и выполнить всю операцию, используя одну транзакцию. Насколько мне известно, это нарушает архитектуру REST, потому что вы, по сути, туннелируете вызов RPC через POST, а не работаете с отдельными ресурсами.

Ответы:


91

Рассмотрим сценарий корзины покупок RESTful. Корзина покупок является концептуально вашей упаковкой транзакций. Таким же образом, как вы можете добавить несколько товаров в корзину покупок, а затем отправить эту корзину для обработки заказа, вы можете добавить запись учетной записи Боба в оболочку транзакции, а затем запись учетной записи Билла в оболочку. Когда все части на месте, тогда вы можете POST / PUT оболочки транзакции со всеми компонентами компонента.


18
Почему TransferMoneyTransaction не будет жизнеспособным банковским ресурсом?
Даррел Миллер

8
Если вы уверены, что ваши конечные точки ссылаются на существительные, то обычно интуитивно понятно, что стандартные глаголы GET, PUT, POST, DELETE будут делать с этим существительным. RPC позволяет конечным точкам самим быть глаголами, и поэтому они могут конфликтовать с глаголами HTTP, и намерение становится запутанным.
Даррел Миллер

10
Например, что произойдет, если вы выполните HTTP-УДАЛЕНИЕ на конечной точке UpdateXYZ? Это удаляет XYZ? Удаляет ли это обновление или просто выполняет обновление и игнорирует удаление глагола HTTP. Держа глаголы вне конечной точки, вы устраняете путаницу.
Даррел Миллер

5
А как насчет транзакций между несколькими службами? и как насчет того, когда вы хотите сделать набор «несвязанных» изменений, которые сервис не предоставляет неявным контейнером транзакций ... плюс, зачем нужен конкретный тип транзакции, когда мы переходим к транзакциям общего назначения, которые совершенно не связаны с вашими фактическими данными меняется. Транзакции могут не совпадать с restful, но кажется, что транзакции должны быть многослойными, не связанными с остальными вызовами, за исключением того факта, что заголовки запроса будут содержать ссылку на транзакцию.
meandmycode

4
@meandmycode Транзакции базы данных должны располагаться за интерфейсом REST. В качестве альтернативы вы можете представить бизнес-транзакции (не транзакции базы данных) как сам по себе ресурс, а затем вам необходимо предпринять компенсирующее действие в случае сбоя.
Даррел Миллер

60

Есть несколько важных случаев, на которые этот вопрос не получил ответа, что я считаю слишком плохим, потому что он имеет высокий рейтинг в Google по поисковым запросам :-)

В частности, было бы неплохо: если вы выполняете POST дважды (из-за того, что некоторый кеш работает в промежуточном состоянии), вы не должны передавать сумму дважды.

Чтобы добраться до этого, вы создаете транзакцию как объект. Он может содержать все данные, которые вы уже знаете, и переводить транзакцию в состояние ожидания.

POST /transfer/txn
{"source":"john's account", "destination":"bob's account", "amount":10}

{"id":"/transfer/txn/12345", "state":"pending", "source":...}

Как только у вас есть эта транзакция, вы можете зафиксировать ее, например:

PUT /transfer/txn/12345
{"id":"/transfer/txn/12345", "state":"committed", ...}

{"id":"/transfer/txn/12345", "state":"committed", ...}

Обратите внимание, что множественные путы на данном этапе не имеют значения; даже GET на TXN вернет текущее состояние. В частности, второй PUT обнаружит, что первый уже находится в соответствующем состоянии, и просто вернет его - или, если вы попытаетесь перевести его в состояние «отката» после того, как он уже находится в «зафиксированном» состоянии, вы получите ошибка, и фактическая совершенная транзакция возвращается.

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


Интересная дискуссия! Хочу добавить, что начальный пост должен быть выполнен за один шаг. Его нельзя добавить позже (тогда мы находимся на территории корзины покупок, и в корзинах есть много чеков и противовесов, чтобы они не причиняли вреда конечному пользователю, даже в соответствии с законодательством, банковские переводы этого не делают) ...
Эрк

33

В терминах REST ресурсы - это существительные, на которые можно воздействовать с помощью глаголов CRUD (создание / чтение / обновление / удаление). Поскольку глагола «перевод денег» не существует, нам необходимо определить ресурс «транзакции», на который можно воздействовать с помощью CRUD. Вот пример в HTTP + POX. Первый шаг - СОЗДАНИЕ (метод HTTP POST) новой пустой транзакции:

POST /transaction

Это возвращает идентификатор транзакции, например, «1234» и в соответствии с URL «/ транзакция / 1234». Обратите внимание, что запуск этого POST несколько раз не приведет к созданию одной и той же транзакции с несколькими идентификаторами, а также позволит избежать введения состояния ожидания. Кроме того, POST не всегда может быть идемпотентным (требование REST), поэтому обычно рекомендуется минимизировать данные в POST.

Вы можете оставить генерацию идентификатора транзакции клиенту. В этом случае вам потребуется POST / транзакция / 1234 для создания транзакции «1234», и сервер вернет ошибку, если она уже существует. В ответе об ошибке сервер может вернуть текущий неиспользуемый идентификатор с соответствующим URL-адресом. Не следует запрашивать у сервера новый идентификатор с помощью метода GET, поскольку GET никогда не должен изменять состояние сервера, а создание / резервирование нового идентификатора изменяет состояние сервера.

Далее мы ОБНОВЛЯЕМ (метод PUT HTTP) транзакцию со всеми данными, неявно фиксируя ее:

PUT /transaction/1234
<transaction>
  <from>/account/john</from>
  <to>/account/bob</to>
  <amount>100</amount>
</transaction>

Если транзакция с идентификатором «1234» ранее была PUT, сервер выдает ответ об ошибке, в противном случае - ответ OK и URL-адрес для просмотра завершенной транзакции.

NB: в / account / john, "john" должен действительно быть уникальным номером счета Джона.


4
Приравнивать REST к CRUD - серьезная ошибка. POST не обязательно должен означать CREATE.

12
Серьезная ошибка? Я знаю, что есть различия между PUT и POST, но есть слабое отображение в CRUD. "Шутки в сторону"?
Тед Джонсон

3
Да серьезно. CRUD - это способ структурировать хранилище данных; REST - это способ структурирования потока данных приложения. Вы можете сделать CRUD на REST, но вы не можете сделать REST на CRUD. Они не эквивалентны.
Джон Уотт

20

Отличный вопрос, REST в основном объясняется примерами, подобными базам данных, где что-то хранится, обновляется, извлекается, удаляется. Подобных примеров несколько, где сервер должен каким-то образом обрабатывать данные. Я не думаю, что Рой Филдинг включил что-либо в свой тезис, который в конце концов был основан на http.

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

Я думал об этом, и мне кажется разумным, чтобы сервер обрабатывал что-то для вас, когда вы загружаете, сервер автоматически создает связанные ресурсы и дает вам ссылки на них (на самом деле это не не нужно автоматически создавать их: он может просто сказать вам ссылки, и он будет создавать их только тогда, когда вы последуете за ними - ленивое создание). А также, чтобы дать вам ссылки для создания новых связанных ресурсов - связанный ресурс имеет тот же URI, но длиннее (добавляет суффикс). Например:

  1. Вы загружаете ( POST ) представление концепции транзакции со всей информацией. Это похоже на вызов RPC, но на самом деле создает «предложенный ресурс транзакции». например, URI: /transaction глюки будут вызывать создание нескольких таких ресурсов, каждый с разным URI.
  2. В ответе сервера указывается URI созданного ресурса, его представление - это включает ссылку ( URI ) для создания связанного ресурса нового « ресурса подтвержденной транзакции».Другие связанные ресурсы - это ссылка для удаления предложенной транзакции. Это состояния конечного автомата, за которыми может следить клиент. Логически они являются частью ресурса, который был создан на сервере, помимо информации, предоставленной клиентом. например , идентификаторы URI: /transaction/1234/proposed, /transaction/1234/committed
  3. Вы размещаете ссылку на создать «ресурс совершенной транзакции» , который создает этот ресурс, изменяя состояние сервера (баланс двух учетных записей) **. По своей природе этот ресурс может быть создан только один раз и не может быть обновлен. Поэтому глюки, совершающие много транзакций, не могут возникнуть.
  4. Вы можете получить эти два ресурса, чтобы увидеть, каково их состояние. Предполагая, что POST может изменить другие ресурсы, предложение теперь будет помечено как «зафиксированное» (или, возможно, вообще недоступное).

Это похоже на то, как работают веб-страницы: на последней странице написано: «Вы уверены, что хотите это сделать?» Эта конечная веб-страница сама является представлением состояния транзакции, которое включает в себя ссылку для перехода в следующее состояние. Не только финансовые операции; также (например) предварительный просмотр, а затем зафиксировать в Википедии. Я предполагаю, что различие в REST состоит в том, что у каждой стадии в последовательности состояний есть явное имя (его URI).

В реальных транзакциях / продажах часто существуют разные физические документы для разных этапов транзакции (предложение, заказ на покупку, получение и т. Д.). Еще больше для покупки дома, с поселением и т. Д.

OTOH Это похоже на игру с семантикой для меня; Меня не устраивает номинализация преобразования глаголов в существительные, чтобы сделать его RESTful, «потому что он использует существительные (URI) вместо глаголов (вызовы RPC)». т. е. существительное «зафиксированный ресурс транзакции» вместо глагола «совершить эту транзакцию». Я предполагаю, что одним из преимуществ номинализации является то, что вы можете ссылаться на ресурс по имени, вместо того, чтобы указывать его каким-либо другим способом (например, поддерживать состояние сеанса, чтобы вы знали, что такое «эта» транзакция ...)

Но важный вопрос: каковы преимущества этого подхода? т.е. чем этот стиль REST лучше стиля RPC? Является ли методика, которая отлично подходит для веб-страниц, также полезна для обработки информации, помимо хранения / извлечения / обновления / удаления? Я думаю, что ключевым преимуществом REST является масштабируемость; один из аспектов этого заключается в том, что нет необходимости явно поддерживать состояние клиента (но делать его неявным в URI ресурса, а следующие состояния указывать в виде ссылок в его представлении). В этом смысле это помогает. Возможно, это также помогает в наслоении / конвейерной обработке? OTOH только один пользователь будет смотреть на свою конкретную транзакцию, поэтому нет никакого преимущества в том, чтобы кэшировать ее, чтобы другие могли ее прочитать - большой выигрыш для http


Не могли бы вы объяснить, как «отсутствие необходимости сохранять состояние на клиенте» помогает масштабируемости? Что за масштабируемость? Масштабируемость в каком смысле?
Джегедус

11

Если вы отступите, чтобы подвести итоги обсуждения, вполне понятно, что REST не подходит для многих API, особенно когда взаимодействие клиента и сервера по своей сути является состоянием, как это происходит с нетривиальными транзакциями. Зачем прыгать через все предложенные обручи, как для клиента, так и для сервера, чтобы педантично следовать некоторому принципу, который не подходит к проблеме? Лучший принцип - дать клиенту самый простой, естественный и продуктивный способ составить заявку.

Таким образом, если вы действительно выполняете много транзакций (типов, а не экземпляров) в своем приложении, вам действительно не следует создавать RESTful API.


9
Правильно, но какой должна быть альтернатива в случае распределенной архитектуры микросервисов?
Витамон,

11

Я отошел от этой темы на 10 лет. Возвращаясь, я не могу поверить, что религия маскируется под науку, в которую вы вступаете, когда вы гуглите отдых + надежный. Путаница мифическая.

Я бы разделил этот широкий вопрос на три части:

  • Нисходящие услуги. Любой веб-сервис, который вы разрабатываете, будет иметь нисходящие сервисы, которые вы используете, и синтаксис транзакций которых у вас нет другого выбора, кроме как следовать. Вы должны попытаться скрыть все это от пользователей вашего сервиса, и убедиться, что все части вашей работы успешно или неудачно в группе, а затем вернуть этот результат своим пользователям.
  • Ваши услуги. Клиенты хотят получить однозначные результаты при вызовах веб-сервисов, и обычный шаблон REST для выполнения запросов POST, PUT или DELETE непосредственно на основные ресурсы кажется мне плохим и легко улучшаемым способом обеспечения этой уверенности. Если вы заботитесь о надежности, вам необходимо определить запросы действий. Этот идентификатор может быть guid, созданным на клиенте, или начальным значением из реляционной БД на сервере, это не имеет значения. Для идентификаторов, генерируемых сервером, используйте запрос-ответ «preflight» для обмена идентификатором действия. Если этот запрос терпит неудачу или наполовину успешно, нет проблем, клиент просто повторяет запрос. Неиспользованные идентификаторы не причиняют вреда.

    Это важно, потому что позволяет всем последующим запросам быть полностью идемпотентными, в том смысле, что если они повторяются n раз, они возвращают один и тот же результат и больше ничего не вызывают. Сервер хранит все ответы против идентификатора действия, и если он видит тот же запрос, он воспроизводит тот же ответ. Более полное описание шаблона в этом документе Google . Документ предлагает реализацию, которая, я считаю (!), В целом следует принципам REST. Эксперты наверняка скажут мне, как это нарушает других. Этот шаблон может быть полезен для любого небезопасного вызова вашего веб-сервиса, независимо от того, вовлечены ли в него нисходящие транзакции.
  • Интеграция вашего сервиса в «транзакции», контролируемые вышестоящими сервисами. В контексте веб-сервисов полные ACID-транзакции обычно не стоят усилий, но вы можете значительно помочь потребителям ваших услуг, предоставив ссылки отмены и / или подтверждения в вашем ответе на подтверждение, и, таким образом, добиться транзакций за счет компенсации .

Ваше требование является фундаментальным. Не позволяйте людям говорить вам, что ваше решение не кошерное. Оцените их архитектуру в свете того, насколько хорошо и насколько просто они решают вашу проблему.


9

Вы должны были бы свернуть свой собственный «идентификатор транзакции» тип управления TX. Так было бы 4 звонка:

http://service/transaction (some sort of tx request)
http://service/bankaccount/bob (give tx id)
http://service/bankaccount/john (give tx id)
http://service/transaction (request to commit)

Вам придется обрабатывать сохранение действий в БД (если балансировка нагрузки) или в памяти или что-то подобное, затем обрабатывать коммит, откат, тайм-аут.

Не очень хороший день в парке.


4
Я не думаю, что это особенно хорошая иллюстрация. Вам нужно только два шага: Создать транзакцию (создает транзакцию в состоянии «ожидания») и Зафиксировать транзакцию (фиксируется, если не передано, и переводит ресурс в состояние фиксации или отката).
Джон Уотт

2

Я думаю, что в этом случае вполне приемлемо нарушить чистую теорию REST в этой ситуации. В любом случае, я не думаю, что на самом деле в REST есть что-то, что говорит о том, что вы не можете касаться зависимых объектов в бизнес-кейсах, которым это требуется.

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


2

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

POST: accounts/alice, new Transfer {target:"BOB", abmount:100, currency:"CHF"}.

Готово. Вам не нужно знать, что это транзакция, которая должна быть атомарной и т. Д. Вы просто переводите деньги иначе. отправить деньги от А до Б.


Но для редких случаев здесь общее решение:

Если вы хотите сделать что-то очень сложное, включающее много ресурсов в определенном контексте с множеством ограничений, которые фактически пересекают барьер «что против того, почему» (знание бизнеса и внедрения), вам нужно перенести состояние. Поскольку REST не должен содержать состояния, вам, как клиенту, необходимо перенести состояние в другое.

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

Таким образом, можно передавать внутреннее состояние, используя шифрование и подписывание системы, и при этом быть в целости и сохранности. Поиск правильной абстракции для клиента, почему он передает информацию о состоянии, зависит от дизайна и архитектуры.


Реальное решение:

Помните, что REST говорит о HTTP, а HTTP идет с концепцией использования куки. Эти куки часто забываются, когда люди говорят о REST API, рабочих процессах и взаимодействиях, охватывающих несколько ресурсов или запросов.

Помните, что написано в Википедии о HTTP-куки:

Файлы cookie были разработаны для того, чтобы веб-сайты могли надежно запоминать информацию о состоянии (например, элементы в корзине) или записывать действия пользователя по просмотру (включая нажатие на определенные кнопки, вход в систему или запись страниц, которые посетитель посетил до сих пор). назад, как месяцы или годы назад).

В общем, если вам нужно передать состояние, используйте куки. Он разработан по той же самой причине, это HTTP, и поэтому он совместим с REST по дизайну :).


Лучшее решение:

Если вы говорите о клиенте, выполняющем рабочий процесс с несколькими запросами, вы обычно говорите о протоколе. Каждая форма протокола поставляется с набором предварительных условий для каждого потенциального шага, например, выполнить шаг A, прежде чем вы сможете выполнить B.

Это естественно, но раскрытие протокола клиентам усложняет все. Чтобы избежать этого, просто подумайте, что мы делаем, когда нам приходится делать сложные взаимодействия и вещи в реальном мире ... Мы используем агента.

Используя метафору Агента, вы можете предоставить ресурс, который может выполнить все необходимые для вас действия, и сохранить фактическое назначение / инструкции, на которые он действует, в своем списке (чтобы мы могли использовать POST для агента или «агентства»).

Сложный пример:

Покупка дома:

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

Эти шаги могут занять несколько дней, некоторые могут быть выполнены параллельно и т. Д.

Для этого вы просто даете агенту задание купить дом вроде:

POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}.

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

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


1

Вы не должны использовать транзакции на стороне сервера в REST.

Один из недостатков ОТДЫХА:

Stateless

Связь клиент-сервер дополнительно ограничена тем, что между запросами не сохраняется клиентский контекст на сервере. Каждый запрос от любого клиента содержит всю информацию, необходимую для обслуживания запроса, и любое состояние сеанса сохраняется в клиенте.

Единственный способ RESTful - создать журнал повторных транзакций и перевести его в состояние клиента. С запросами клиент отправляет журнал повторов, а сервер повторяет транзакцию и

  1. откатывает транзакцию назад, но предоставляет новый журнал повторных транзакций (еще один шаг)
  2. или, наконец, завершить транзакцию.

Но, возможно, проще использовать технологию, основанную на сеансах сервера, которая поддерживает транзакции на стороне сервера.


Цитата из записи REST в Википедии. Это реальный источник или Википедия откуда-то его получила? Кто скажет, что такое клиентский контекст и что такое серверный контекст?
bbsimonbb

1

Я полагаю, что это был бы случай использования уникального идентификатора, сгенерированного на клиенте, чтобы гарантировать, что сбой соединения не подразумевает двуличность, сохраненную API.

Я думаю, что использование сгенерированного клиентом поля GUID вместе с объектом переноса и обеспечение того, чтобы тот же GUID не был повторно вставлен снова, было бы более простым решением вопроса банковского перевода.

Не знаю о более сложных сценариях, таких как бронирование нескольких авиабилетов или микро-архитектуры.

Я нашел статью на эту тему, касающуюся опыта работы с атомарностью транзакций в сервисах RESTful .


0

В простом случае (без распределенных ресурсов) вы могли бы рассматривать транзакцию как ресурс, где процесс ее создания достигает конечной цели.

Таким образом, для перехода между <url-base>/account/aи <url-base>/account/bвы можете опубликовать следующее <url-base>/transfer.

<Передача>
    <С> <URL базы> / счет / а </ с>
    <В> <URL базы> / счет / б </ к>
    <Сумма> 50 </ количество>
</ Передача>

Это создаст новый ресурс переноса и вернет новый URL переноса, например <url-base>/transfer/256.

В момент успешной публикации на сервере выполняется «реальная» транзакция, а сумма удаляется из одной учетной записи и добавляется в другую.

Это, однако, не распространяется на распределенную транзакцию (если, скажем, «a» хранится в одном банке за одной услугой, а «b» - в другом банке за другой услугой), за исключением того, чтобы сказать «попытаться сформулировать все операции способами, которые не требуют распределенных транзакций ".


2
Если вы не можете «сформулировать все операции способами, которые не требуют распределенных транзакций», то вам действительно нужен двухфазный коммит. Лучшая идея, которую я мог бы найти для реализации двухэтапной фиксации в REST - это rest.blueoxen.net/cgi-bin/wiki.pl?TwoPhaseCommit , которая, что важно, не портит пространство имен URL и позволяет двухэтапной фиксации накладываться на несколько слоев. чистая семантика REST.
Phasmal

3
Другая проблема с этим предложением состоит в том, что, если кэш-память икоты и POST дважды, вы получаете две передачи.
Джон Уотт

Правда, в этом случае вам потребуется двухэтапный процесс - создайте ресурс «Transfer» с уникальным URL-адресом, а затем добавьте к нему детали переноса как часть коммита (две части, как указано в других ответах). Конечно, это можно было бы сформулировать как создание ресурса «транзакция», а затем добавление к нему операции «передача».
Фазмал

-3

Я думаю, вы могли бы включить TAN в URL / ресурс:

  1. PUT / транзакция для получения идентификатора (например, «1»)
  2. [PUT, GET, POST, что угодно] / 1 / account / bob
  3. [PUT, GET, POST, что угодно] / 1 / счет / счет
  4. УДАЛИТЬ / транзакция с ID 1

Просто идея.


Я вижу две проблемы с этим подходом: 1) это означает, что вы не можете получить доступ к ресурсу вне транзакции (хотя, возможно, это не имеет большого значения). 2) Ни один из ответов до сих пор не касался того факта, что сервер больше не находится в состоянии без сохранения состояния, хотя я подозреваю, что с этим ничего нельзя поделать.
Гили

Ну, / 1 / account / bob и / account / bob - это просто два разных ресурса. :) И RE: без сохранения состояния, это означает, что ресурс всегда доступен и не зависит от предыдущего запроса. Поскольку вы запросили транзакции, да, это не так. Но опять же, вы хотели транзакции.
до

1
Если клиент должен собрать URI, то ваш API не является RESTful.
aehlke

1
Я не могу понять вас, ребята, правда! Если вы рассматриваете транзакцию как ресурс (как в примере выше), вы просто прекращаете обрабатывать транзакцию в классическом смысле и используете ее «надлежащим способом REST», что еще больше упрощает программирование транзакционных процессов. Например, вы можете включить ссылку в транзакцию в свои ответы, чтобы обойти смещение в распределенной серверной среде, она все еще не имеет состояния (это просто ресурс, не так ли?), И вы можете реализовать механизм транзакций в любом случае, если вы хочу (что, если у вас нет БД в спину?)
Матиас Гринишак

1
Так или иначе, если вы просто перестанете думать о SQL / SOAP и начнете думать о HTTP (как это делает браузер), все станет просто
Матиас Хринисак,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.