Краткий экскурс в GraphQL

https://www.robinwieruch.de/why-graphql-advantages-disadvantages-alternatives/
  • Перевод
Привет, Хабр!


Именно кратким экскурсом в язык запросов GraphQL послужит вам книга Алекса Бэнкса и Евы Порселло, которую мы отдали в перевод пару дней назад. Книга этих же авторов о React и Redux стала настоящим бестселлером (ждем 5-й тираж из типографии). Кстати, спасибо всем, кто указал нам на неточности в коде и терминах ;) книгу по столь быстро устаревающей технологии мы делали излишне быстро.

Автор сегодняшней статьи Робин Вирух также работает над книгой о GraphQL и библиотеках для этого языка, а в сегодняшней статье кратко объясняет достоинства и характерные особенности GraphQL как альтернативы REST



Когда заходит речь о сетевых запросах между клиентскими и серверными приложениями, чаще всего в качестве мостика между клиентским и серверным мирами выбирают REST. В REST все развивается вокруг идеи «нам нужны ресурсы, доступные по URL». Можно считывать ресурс при помощи запроса HTTP GET, создавать ресурс при помощи запроса HTTP POST, обновлять и удалять его при помощи запросов HTTP PUT и DELETE. Эти операции называются CRUD (Create, Read, Update, Delete). В качестве ресурса может выступать любое содержимое, полученное от авторов, пользователей или взятое из статей. При использовании REST формат передачи данных жестко не задан, но чаще всего для этой цели используется JSON. В конце концов, REST обеспечивает коммуникацию между приложениями по обычному протоколу HTTP с применением URL и HTTP-методов.

// запрос в стиле REST
GET https://api.domain.com/authors/7

// Отклик в JSON
{
  "id": "7",
  "name": "Robin Wieruch",
  "avatarUrl": "https://domain.com/authors/7",
  "firstName": "Robin",
  "lastName": "Wieruch"
}

Хотя, достаточно долго REST и оставался де-факто стандартом, в последние годы стала набирать популярность другая технология, разработанная в Facebook: она называется GraphQL. Эта статья – введение в GraphQL, рассказывает о достоинствах и недостатках данного языка запросов.

Что такое GraphQL?

Прежде чем погрузиться в обсуждение достоинств и недостатков GraphQL, давайте для начала ответим на следующий вопрос: а что такое GraphQL? GraphQL – это свободно распространяемый язык запросов, созданный в компании Facebook в 2012 году. Еще до предоставления продукта в опенсорс язык уже использовался в Facebook в качестве внутрикорпоративной технологии для работы с мобильными приложениями. Почему именно с мобильными приложениями? GraphQL разработали в качестве альтернативы типичной REST-архитектуры. Он позволяет клиенту лишь запросить желаемые данные – ни больше, ни меньше. За все отвечает клиент, то есть, вы. В REST-архитектуре в таком случае возникают сложности, поскольку именно интерфейс базы данных определяет, какая информация будет доступна каждому ресурсу по каждому URL. Выборка данных запрашивается не в клиентской части. Поэтому фронтенд в любом случае должен запросить всю информацию о ресурсе, даже если ему нужна лишь часть этих данных. Данная проблема называется «перевыборка». В худшем сценарии клиентскому приложению приходится читать даже не один, а множество ресурсов, для обращения к которым приходится выполнить множество сетевых запросов. Это приводит не только к перевыборке, но и к лавинообразным запросам по сети. Однако, имея такой язык запросов как GraphQL, используемый не только на серверной, но и на клиентской стороне, сам клиент решает, какие данные ему нужны – и для этого отправляет на сервер всего один запрос. Когда в Facebook разрабатывали мобильные приложения с применением языка GraphQL, удалось радикально снизить нагрузку сети, поскольку по ней стало передаваться гораздо меньше данных.

Facebook выложил в свободный доступ спецификацию GraphQL и его справочную реализацию на языке JavaScript. С тех пор эта спецификация была реализована на многих других крупных языках программирования. Кроме того, экосистема, сложившаяся вокруг GraphQL, растет не только горизонтально, распространяясь на другие языки программирования, но и вертикально (поверх GraphQL надстраиваются библиотеки, например, Apollo, Relay).

В GraphQL предусмотрены такие виды операций: запрос (считывание), изменение (запись) или подписка (непрерывное считывание). Любая из таких операций – просто строка, которую необходимо собрать в соответствии со спецификацией языка запросов GraphQL. Как только такая операция GraphQL придет в приложение базы данных с клиентского приложения, ее можно будет интерпретировать в сравнении со всей схемой GraphQL, расположенной на бэкенде, и разрешать для клиентского приложения при помощи имеющихся данных. GraphQL с равным успехом работает с любым сетевым уровнем (который зачастую организуется по протоколу HTTP), а также с любым форматом полезной нагрузки (зачастую это JSON). Также его совершенно «не волнует» архитектура приложения (которое в большинстве случаев состоит из клиентской части и интерфейса базы данных). Это просто язык запросов.

// запрос GraphQL 
author(id: "7") {
  id
  name
  avatarUrl
  articles(limit: 2) {
    name
    urlSlug
  }
}

// результат запроса GraphQL
{
  "data": {
    "author": {
      "id": "7",
      "name": "Robin Wieruch",
      "avatarUrl": "https://domain.com/authors/7",
      "articles": [
        {
          "name": "The Road to learn React",
          "urlSlug": "the-road-to-learn-react"
        },
        {
          "name": "React Testing Tutorial",
          "urlSlug": "react-testing-tutorial"
        }
      ]
    }
  }
}

Как видите, запрос уже обращается за множеством ресурсов (автор, статья), которые в GraphQL называются полями, и лишь конкретный набор вложенных полей для этих полей (name, urlSlug для статьи), хотя, в самой GraphQL-схеме данных может предоставляться и прочая информация (например, для статьи – описание, дата выхода). Тогда как в REST-архитектуре нам потребовалось бы как минимум два каскадирующих запроса для извлечения сущности «автор» и статей этого автора, GraphQL решает эту задачу за один запрос. Кроме того, при запросе выбираются лишь необходимые поля, а не вся сущность целиком.

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

Преимущества GraphQL

Далее перечислены основные выгоды от использования GraphQL в приложении.

Декларативная выборка данных

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

Никакой перевыборки при работе с GraphQL

При работе с GraphQL перевыборки не бывает. Тогда как мобильный клиент, скорее всего, может удариться в перевыборку, используя тот же самый API, что и веб-клиент с REST-API. А при работе с GraphQL мобильный клиент и веб-клиент могут выбирать для себя разные группы полей, используя при этом один и тот же GraphQL API. Следовательно, мобильный клиент может выбрать меньше информации, поскольку лишняя информация может быть не нужна на маленьком экранчике (в отличие от большого монитора, с которого просматривается веб-версия приложения). GraphQL минимизирует объем данных, передаваемых по сети, избирательно подбирая их и руководствуясь при этом в первую очередь потребностями клиентского приложения.

GraphQL для React, Angular, Node и пр.

GraphQL — многообещающее решение не только для React-разработчиков. Пусть именно Facebook козырнул GraphQL, а на стороне клиента у Facebook используется React, на самом деле, этот язык не завязан ни на одно решение для фронтенда или бэкенда. Справочная реализация GraphQL написана на JavaScript, поэтому GraphQL можно сочетать с Angular, Vue, Express, Hapi, Koa и другими JavaScript-библиотеками в клиентской и серверной части. Причем, это касается не только экосистемы JavaScript. GraphQL подражает REST в одном аспекте, благодаря которому тот и стал популярен: интерфейс GraphQL не зависит от языка программирования (языка запросов), применяемого для коммуникации двух объектов (напр., клиента и сервера). Следовательно, его спецификацию можно воспроизвести на любом языке программирования.

Кто использует GraphQL?

Facebook использовал GraphQL с 2012, еще до перехода этого языка в опенсорс. Именно Facebook – та движущая сила, что отвечает за разработку спецификации GraphQL и ее справочной реализации на языке JavaScript. Итак, работая с GraphQL, вы уже стоите на плечах гигантов. Однако, и другие хорошо известные компании применяют этот язык в своих приложениях. Они инвестируют в экосистему GraphQL, поскольку современные приложения колоссально нуждаются именно в таком языке. Итак, опорой вам послужат не только Facebook, но и следующие компании:


Когда Facebook разработала GraphQL и предоставила в открытый доступ, другие компании, создававшие мобильные приложения, также сталкивались со схожими проблемами. Именно так Netflix и создал проект Falcor, который можно считать альтернативой GraphQL. Что в лишний раз подтверждает, что для современных приложений нужны именно такие решения как GraphQL и Falcor.

Единый источник истины

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

GraphQL следует современным тенденциям

GraphQL следует современным тенденциям в создании приложений. У вас на бэкенде может быть всего одно приложение, но зачастую бывает так, что этим бэкендом пользуется множество разных клиентов (веб-клиент, мобильное устройство, умные часы…) и все они зависят от данных, хранящихся в бэкендовом приложении. Следовательно, GraphQL может помочь не только подружить «оба мира», но и удовлетворить требования каждого клиента (связанные, например, с использованием сети, вложенными взаимосвязями данных, выборкой лишь требуемых данных) без необходимости создавать выделенный API для каждого типа клиента.

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

Как сшивается схема GraphQL

Благодаря сшиванию можно собирать одну схему из множества других. Когда можно попасть в такую ситуацию? Допустим, ваш бэкенд реализован при помощи микросервисной архитектуры. Каждый микросервис обрабатывает бизнес-логику и данные, относящиеся к конкретной предметной области. Следовательно, каждый микросервис может определить собственную схему GraphQL. После этого понадобится их сшить, чтобы собрать из всех схем одну, к которой и будет обращаться клиентское приложение. В конце концов, у каждого микросервиса может быть собственный терминал GraphQL, а один шлюз GraphQL API будет консолидировать все схемы в одну глобальную, чтобы предоставить ее клиентским приложениям.

Интроспекция GraphQL

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

Сильно типизированный GraphQL

GraphQL – это сильно типизированный язык запросов, написанный на выразительном языке определения схем (SDL) для GraphQL. Этот язык обладает теми же достоинствами, что и любой сильнго типизированный язык программирования. Он менее подвержен ошибкам, допускает валидацию во время компиляции и позволяет рассчитывать на интеграцию с поддерживаемыми возможностями IDE/редактора, такими, как автозавершение и поддержка ввода.

Версионирование GraphQL

В GraphQL нет таких версий API, к которым мы привыкли в REST. В REST нормально предлагать несколько версий одного API (напр. api.domain.com/v1/, api.domain.com/v2/), поскольку ресурсы или их структура со временем могут меняться. В GraphQL можно перевести API в нерекомендуемые на уровне поля. Следовательно, клиент получает предупреждение, когда обращается к нерекомендуемому полю. Спустя некоторое время нерекомендуемое поле может быть исключено из схемы, тогда больше никакие клиенты не будут его использовать. Таким образом, API GraphQL может развиваться без необходимости версионирования.

Растущая экосистема GraphQL

Экосистема GraphQL растет. Речь не только об интеграциях с редакторами и IDE, связанных с сильно типизированной природой GraphQL; для GraphQL как такового появляются новые полноценные варианты применения. Например, можно припомнить Postman, применявшийся при работе с REST API, а теперь для этой же цели, но с GraphQL API применяется GraphiQL или GraphQL Playground. Также для вас найдутся различные библиотеки, например, Gatsby.js, генератор статических веб-сайтов для React, использующий GraphQL. Например, Gatsby.js позволяет написать движок для блога, наполняющий ваш блог контентом во время сборки через GraphQL API. Следовательно, у вас также будут CMS без клиентской части (напр., GraphCMS), предоставляющие контент (для блога) через GraphQL. API. Однако, в этой области развиваются не только технологические компоненты. Как грибы после дождя растут конференции, митапы и сообщества, посвященные GraphQL, также не составляет труда найти по нему новостные рассылки и подкасты.

Если я перехожу на GraphQL – то иду «олл-ин»?

Добавляя GraphQL в имеющийся технологический стек, мы, конечно, не идем «олл-ин». Мигрируя с монолитного бэкендового приложения на микросервисную архитектуру, самое дело подставить API GraphQL для новоиспеченных микросервисов. Ведь именно при наличии множества микросервисов, вы с вашей командой смело можете внедрить шлюз GraphQL, сшивая схемы и консолидируя их в одну глобальную схему. Но шлюз API можно использовать не только с микросервисами, но и с монолитным REST-приложением. Именно так можно объединить все ваши API на одном шлюзе и шаг за шагом мигрировать на GraphQL.

Недостатки GraphQL

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

Сложность запросов GraphQL

Иногда GraphQL используют неправильно, пытаюсь заменить им базу данных на стороне сервера. Нет, так не пойдет. GraphQL – просто язык запросов. Когда на стороне сервера запрос необходимо разрешить данными, обычно найдется не зависящая от GraphQL реализация, обеспечивающая доступ к базе данных. GraphQL в данном случае индифферентен. Более того, GraphQL не устраняет никаких узких мест с производительностью, когда вам требуется обращаться в одном запросе сразу ко множеству полей (авторы, статьи, комментарии). Независимо от того, в какой архитектуре совершался запрос – RESTful или GraphQL, вам все равно придется извлекать из источника различные поля.

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

Ограничение скорости в GraphQL

Другая проблема – ограничение скорости. Тогда как в REST сравнительно просто сказать «допускается не более чем столько запросов в день», сложно сформулировать подобную инструкцию для отдельных операций GraphQL, так как здесь бывают не только «затратные» и «незатратные» операции, но и множество промежуточных градаций. Именно для таких случаев компании, предоставляющие публичные API GraphQL предлагают собственные расчеты по ограничению скорости, зачастую сводимые к вышеупомянутым максимальным значениям глубины запросов и взвешиванию сложности запросов.

Кэширование GraphQL

При работе с GraphQL реализация упрощенного кэша оказывается гораздо сложнее, чем в REST. Работая с REST, мы обращаемся к ресурсам по URL и, следовательно, можем организовать кэширование на уровне ресурсов, так как URL ресурса может служить его идентификатором. В GraphQL это усложняется, так как все запросы могут получаться разными, даже притом, что все оперируют одним и тем же объектом. В одном запросе вы можете затребовать имя автора, а в следующем – не только имя автора, но и адрес его электронной почты. Именно для таких случаев вам понадобится более филигранный кэш на уровне полей, а реализовать его не так-то просто. Однако, большинство библиотек, построенных поверх GraphQL, предлагают такие механизмы кэширования прямо «из коробки».

Почему не REST?

GraphQL – это альтернатива широко распространенной архитектуре REST, соединяющей клиентские и серверные приложения. Выше мы неоднократно упоминали REST – так каковы же очевидные выгоды GraphQL, ради которых его стоит предпочесть REST?
Поскольку в REST предусматривается URL для каждого ресурса, зачастую у нас получаются неэффективные каскадирующие запросы. Сначала мы выбираем объект «автор», идентифицируемый по id, а затем выбираем все статьи этого автора, помеченные его id. В GraphQL все это можно сделать всего за один запрос, и в этом отношении он гораздо эффективнее. Более того, если вы хотите выбрать все статьи автора, а информацию об авторе не трогать, то GraphQL позволяет вам вычленить лишь те информационные фрагменты, которые вам нужны.

Современные клиентские приложения не рассчитаны на работу с серверными приложениями, устроенными по принципу REST. Возьмем к примеру результат поиска на платформе Airbnb. Вам выводятся дома, впечатления о них и другие сопряженные ресурсы. Дома и впечатления сами про себе будут REST-ресурсами, поэтому в REST-архитектуре вам потребуется выполнять множество запросов по сети. Если же у вас, напротив, будет GraphQL API, то все сущности можно будет затребовать в одном запросе GraphQL, соотнеся их бок о бок (например, дома и впечатления), либо в виде вложенных взаимосвязей (напр., статьи от авторов).

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

Наконец, остаются и такие ситуации, в которых REST – ценный способ сочленения клиентских и серверных приложений. Зачастую приложения завязаны на работу с ресурсами, и в них не требуется всех возможностей столь гибкого языка запросов как GraphQL. Однако, рекомендую хотя бы попробовать GraphQL, когда будете приступать к разработке вашей следующей клиент-серверной архитектуры.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Хотите перевод статьи этого же автора о библиотеках для работы с GraphQL?

Издательский дом «Питер»

237,00

Компания

Поделиться публикацией

Похожие публикации

Комментарии 18
    +2

    Ох… хоть статью пиши: "Что не так с GraphQL".


    После года с лишним разработки с использованием этого языка, могу смело заявить: всё совсем не так радужно, как об этом пишут. В GraphQL есть свои косяки и проблемы. Некоторые легко решаются, с другими сложнее. А ведь есть и фундаментальные ошибки в дизайне самого языка. Один только null чего стоит… косяк того же порядка что и checkbox в HTML.


    Ну и ставить в один ряд с REST я его тоже бы не стал. Это разных плоскостей вещи. REST — архитектурный стиль работающий поверх HTTP. GraphQL — язык запросов/мутаций, совершенно не привязанный к методу транспортировки. Да и под REST'ом сейчас понимают в основном типичный CRUD. Хотя REST, он про другое — про отсутствие состояния (сессий) на клиенте, ресурсы и методы работы с ними.

      +4
      А вот возьмите и напишите.
      Статьи «Что не так с ...» всегда интересны, познавательны и поучительны.
        +1
        Про некоторые «особенности» graphql и как с ним быть (в т.ч. где он полезен) есть
        неплохое видео


        Я для себя тоже решил что GraphQL что-то не то и решил пока своё простое query-api сделать.
          0
          Ну, например, как вы реализуете на REST аналог GraphQL subscriptions?
            0
            Polling, long-polling, если только про REST. Ну и WebSockets, Server Sent Events тоже в помощь.
          0
          Он позволяет клиенту лишь запросить желаемые данные – ни больше, ни меньше. За все отвечает клиент, то есть, вы.

          Скорее схема определенная на серверной части

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

          Тут вообще посмеялся

            0
            Все проблемы, которые якобы решает GQL мы решали лет 10 назад в рамках обычного REST API.
              0
              Милый минусующий коллега, могли бы вы соблаговолить раскрыть вашу точку зрения на мой комментарий несколько шире? Желательно в виде ответного реверанса.
                0
                Минусовал не я, но, как я понимаю, основные фичи graphQL по сути две:
                1. Можно указывать серверу какие поля вернуть в запросе, чтобы убрать лишнюю нагрузку на сеть (такая себе оптимизация)
                2. Можно получать вложенные данные одним запросом (например пост и коментарии к нему)
                REST ничего такого не подразумевает.
                Хотя фичу номер один можно в рест легко добавить скажем как query в url, что-то вроде projection в mongoDB. То вторая фича все же, как по мне, идет несколько в разрез с философией REST, так как пост и комментарии к нему — это два разных ресурса, а значит у них должны быть два разных URL для их получения.
                  0
                  В чем проблема все это сделать в REST API? Сервер и обработку запросов вы же сами все равно пишете)))) Мы именно так и делали и все прекрасно работало.

                  По поводу философии REST и «не соответствует», выше писали что многие превратно ее понимают. В ней самое важное stateless и понятие ресурса. А «ресурс» кстати совершенно не равно таблица БД. На этом многие и ошибаются.

                  Похоже основная сила REST, а именно то, что это именно концепт, сыграла с ним злую шутку. GQL же сразу говорит, мол я просто язык запросов, без философии. Именно поэтому мы с вами и не наблюдаем тонны статей на тему “А канонический ли у вас GQL?”, как это всегда было с REST))
                    +1
                    Да нет проблемы, можно вообще хоть все запросы делать POST или только PUT, кто ж запретит.
                    Мне вообще, кстати, кажется что слишком много внимания уделяется транспортному уровню, слишком много телодвижений, что бы просто вызвать процедуру на сервере и получить результат ее выполнения. Лично для себя я больше нашел привлекателен подход RPC через WebSocket с автоматической генерацией удаленных прокси над use case'ами (как хотите их называйте: модели, сервисы, фасады или как угодно). Получается так что я просто пишу новый метод на сервере и фронт просто вызывает этот же метод удаленного прокси и все общение сводится к словно локальному вызову функций. При этом мне вообще не нужно описывать никаких схем, роутов и прочего. Правда пока так, только на своих личных пэт-проектах такое пробовал.
                    Кстати, не обязательно WebSocket использовать, просто привел пример, так как я это делал. Пусть хоть через HTTP удаленный прокси общается с сервером, но основную идею, думаю, понятно выразил.
                      0
                      Ну зачем же себя ограничивать только POST? Все же HTTP Verbs это важная часть REST.

                      Честно говоря уже точно не помню семантику, но вот пример эквивалентного запроса из статьи в REST стиле:

                      GET /articles?authorId=7&limit=2&_join=authors&_include=name,urlSlug,author.id,author.name,author.avatarUrl
              0
              не в ту ветку ответил (
                0
                а чем тогда JSON RPC, например, не угодил?
                там все также реализуется. да и стандарт старый
                  –1
                  Потому что лицекниге надо все своё)
                  0
                  Меня в GraphQL настораживает безопасность. Как ограничить запросы клиента?

                  Обычно мне на это отвечают — а используйте те средства что вы сейчас используете.

                  Но сейчас у меня "много окон" и я сам, на сервере, определяю кому (по ролям) дать доступ как к окну, так и что он (опять по ролям) получит из этого окна.

                  А вот при GraphQL — где "одно окно" — настроить также просто(!) доступ на получение данных, как я вижу, либо проблематично, либо ещё(!) никто и не парится. Поди.

                    0
                    Есть множество способов это сделать. Я, например, формирую разные схемы для разных клиентов.
                    0
                    ph_piter
                    Скажите какие планы на издательство книг из серии Head First?
                    А так же планируется перевод таких книг как «Head First Algebra» и «Head First Programming»?

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое