{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreiawwaxpnfucnqxdodzpaxuv6lrmiwz7gdxguxmr24bb4aq7it234q",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpq3bnxkfex2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreicmhfah7pgeydm3qcnwacieimzsiqrsvxoelk4nynxreod43zycku"
    },
    "mimeType": "image/webp",
    "size": 431954
  },
  "path": "/adamanq/ash-sposoby-vzaimodieistviia-ashjsonapi-ashgraphql-i-ashtypescript-1h9p",
  "publishedAt": "2026-07-03T07:50:45.000Z",
  "site": "https://dev.to",
  "tags": [
    "elixir",
    "ash",
    "phoenix",
    "backend",
    "Ash Framework: Знакомство",
    "AshJsonApi",
    "jsonapi.org",
    "AshGraphQL",
    "официальное руководство по мониторингу Ash",
    "AshTypescript",
    "телеграм-канале"
  ],
  "textContent": "Разработка стабильного продукта, способного держать высокие нагрузки на разных серверах, — задача сложная и ресурсозатратная. Программист не может сфокусироваться только на бизнес-логике: сначала нужно настроить базу данных, спроектировать серверные **_API_** и многое другое.\n\nСложность многократно возрастает при работе в одиночку. Приходится самому реализовывать интерфейс на выбранном фреймворке, обеспечивать совместимость слоёв, собирать проект в образы **_Docker_** и разворачивать его в **_Kubernetes_**. Добавьте сюда настройку маршрутизации через **_Nginx_** и проектирование системы по принципам чистой архитектуры — и получится крайне трудоёмкий процесс.\n\nПоэтому во многих языках активно внедряют инструменты для автоматизации рутины (_бойлерплейта_), позволяя сосредоточиться на уникальном коде. Например, в **_Go_** есть библиотека **_oapi-codegen_** : вы описываете контракт в формате **_Swagger_** , а по нему автоматически генерируется код сервера. Такой формат упрощает интеграцию фронтенда и бэкенда и делает изменения логики менее болезненными — достаточно обновить описание в **_Swagger_** -файле и перегенерировать сервис.\n\nПодобные подходы к систематизации повторяющихся задач появляются повсеместно. Главная цель — сделать интеграцию предсказуемой и исключить ситуацию, когда разработчику приходится десятки раз переписывать и тестировать одно и то же ради идеальной совместимости. И в этой сфере есть свой лидер — **Ash**!\n\nㅤ\n\n\nㅤ\n\n\n##  🙏 Благодарности\n\nХочу выразить глубокую признательность Заку Даниэлю и всем, кто отреагировал на мою предыдущую статью об **_Ash_** (Ash Framework: Знакомство) и поделился ею. Ваша поддержка дала огромный заряд энергии и мотивации и подтвердила, что сообщество **_Ash_** — потрясающее!\n\nㅤ\n\n\n##  📖 Предисловие\n\nВ этом материале намеренно не будет практическое руководство в полном объёме по указанным темам, так как полагаемся на вашу любознательность. Несмотря на желание охватить всё, я не смогу в одной статье описать всю практическую сторону данных фреймворков и научить вас всему и сразу. Вместо этого я поделюсь с вами информацией и задам направление для размышлений, что и является основной целью данной публикации. Для более глубокого погружения в работу этих библиотек я рекомендую вам начать создавать на их основе собственные проекты — именно такой подход когда-то помог мне лучше всего разобраться в их устройстве. Помните, что главным наставником для вас станет практика, поэтому успехов в её освоении!\n\nВажно: цель статьи — вовсе не уговорить вас использовать Ash повсеместно и постоянно!\n\nㅤ\n\n\nㅤ\n\n\n##  🧐 Что предлагает экосистема Ash\n\nЕсли вы знакомы с **_Elixir_** и **_Phoenix_** , то знаете, что фреймворк уже содержит встроенные механизмы для упрощения разработки. Один из них — **_LiveView_**. Он позволяет отказаться от классического разделения на **_API_** -бэкенд и отдельный **_JavaScript_** -фронтенд: интерфейс пишется прямо внутри приложения. Это ускоряет разработку и удобно бэкенд-программистам, работающим без фронтенд-команды. Для простых и средних проектов путь с **_LiveView_** отлично подходит, но при создании масштабных систем код рискует превратиться в «спагетти», где трудно отслеживать зависимости между компонентами и находить обработчики вроде `def handle_params/3`.\n\nА как быть, если нужен качественный обособленный фронтенд, но вы устали каждый раз заново писать шаблонный **_REST_** -интерфейс и настраивать связь? Ответ — освоить возможности **_Ash_**. О них и поговорим.\n\nㅤ\n\n\nㅤ\n\n\n##  😮 REST с AshJsonApi: новый подход\n\nСтандартная практика подразумевает фиксацию контракта между фронтендом и бэкендом не только на словах, но и документально — обычно через **_Swagger_**. В нём описывают структуры данных и методы API, после чего документация передаётся команде интерфейса. Любые изменения требуют осторожности, чтобы не сломать совместимость. Даже базовая настройка такого подхода стоит немалых усилий: нужно интегрировать библиотеку генерации, создать конфигурации по спецификации OpenAPI (_Swagger_) и вручную описать конечные точки (_endpoints_). Если речь о переписывании существующего проекта, процесс легко занимает больше рабочего дня.\n\nAshJsonApi предлагает альтернативу. Главное преимущество — вам больше не нужно предварительно писать спецификацию в **_YAML/JSON_** и генерировать из неё код. Для старта достаточно готового домена ресурсов и подключённого расширения **_AshJsonApi_**.\n\nСоздать новый проект с поддержкой этого инструмента можно прямо на главной странице портала **_Ash_** через конструктор. Если проект на **_Ash_** уже есть, добавить функциональность можно одной командой:\n\n\n\n    mix igniter.install ash_json_api\n\n\nВ процессе установки система задаёт уточняющие вопросы (_Y/N_). Можно соглашаться со всеми предложениями или изучить каждый пункт и решить самостоятельно. После установки останется выполнить несколько простых шагов.\n\n###  Настройка ресурса для работы с JSON:API\n\nЧтобы ресурсы стали доступны через **_API_** , подключите расширение **_AshJsonApi.Resource_**. Это позволит автоматически сгенерировать эндпоинты на основе определений домена. Добавьте `AshJsonApi.Resource` в список `extensions` и задайте имя ресурса в параметре `type`:\n\n\n\n    defmodule CourseHub.Courses.Lesson do\n      use Ash.Resource, extensions: [AshJsonApi.Resource]\n\n      # Активация генерации эндпоинтов\n      json_api do\n        type \"lesson\"\n      end\n\n      actions do\n        defaults [:read, :create, :destroy, :update]\n\n        action :list_lessons_allowed, :map do\n          run fn _input, _ctx ->\n            allowed_courses = %{\"go\" => \"Golang\", \"elixir\" => \"Elixir\", \"rust\" => \"Rust\"}\n            {:ok, allowed_courses}\n          end\n        end\n      end\n    end\n\n\nПараметр `type` определяет имя ресурса в пространстве имён JSON:API. Он используется преимущественно для документации (_например, в**Swagger** или **Redoc**_) и позволяет группировать методы по тегам.\n\nДалее опишите маршруты в домене, указав пути и связав их с конкретными действиями ресурса:\n\n\n\n    defmodule CourseHub.Courses do\n      use Ash.Domain, extensions: [AshJsonApi.Domain]\n\n      json_api do\n        routes do\n          base_route \"/lessons\", CourseHub.Courses.Lesson do\n            index :read, description: \"Получение списка лекций\"\n            get :read, primary?: true, description: \"Получение одной лекции\"\n            post :create, description: \"Создание лекции\"\n            patch :update, description: \"Изменение лекции\"\n            delete :destroy, description: \"Удаление лекции\"\n\n            # Путь относителен base_route, итоговый эндпоинт: /lessons/allowed\n            route :get, \"/allowed\", :list_lessons_allowed,\n              description: \"Получение списка разрешённых лекций\"\n          end\n        end\n      end\n    end\n\n\nВыполнив эти шаги, вы получите шесть полностью функциональных эндпоинтов. Вся логика теперь связана с вашими **_actions_** внутри доменной модели. Отдельные **_Swagger_** -файлы или ручное определение маршрутов не нужны: изменение логики action в коде автоматически отражается на поведении соответствующего **_API_** -эндпоинта.\n\n###  Ключевые достоинства AshJsonApi\n\n####  Единый подход и предсказуемость\n\nОсновное преимущество — строгая согласованность приложения. При переходе на другой тип контракта (_например,**GraphQL**_) не придётся переписывать бизнес-логику. Поскольку **_AshJsonApi_** работает поверх ваших **_actions_** , вы застрахованы от ситуации, когда изменения в коде не отразились в одном из интерфейсов: вся логика инкапсулирована в **_action_** , который вы «раздаёте» разным контрактам. Это заметно ускоряет разработку — по личным оценкам, готовый продукт получается примерно в пять раз быстрее. Для автоматизации можно воспользоваться командой:\n\n\n\n    mix ash.patch.extend CourseHub.Courses.Lesson json_api\n\n\nУказав адрес ресурса, вы позволите **_AshJsonApi_** автоматически сгенерировать всю обвязку для работы с **_API_**.\n\n####  Работа с данными: фильтрация, сортировка и связи\n\nКак бэкенд-разработчик, я раньше тратил много времени на рутинную реализацию фильтров и сортировок, вручную составляя **_SQL_** с **_LIMIT_** , **_OFFSET_** и **_WHERE_**. **_AshJsonApi_** избавляет от этого: фильтрация и упорядочивание выполняются автоматически. Фронтенду достаточно указать нужные поля и условия прямо в запросе. Это кардинально упрощает взаимодействие команд: фронтенд формирует запросы к данным самостоятельно, не отвлекая бэкенд. При этом все вычисления происходят на сервере, поэтому клиент не несёт лишней нагрузки — он лишь описывает желаемую схему данных. Полный список параметров есть в официальной документации. Пример запроса:\n\n\n\n    GET /courses/1?include=lessons&included_page[lessons][limit]=10&included_page[lessons][offset]=20\n\n\nЗапрос возвращает информацию о курсе и подгружает связанные лекции с заданной пагинацией. Ответ приходит в формате **_application/vnd.api+json_** — это стандартизированная спецификация (подробнее на jsonapi.org), которая структурирует JSON для предсказуемой работы.\n\nПример ответа:\n\n\n\n    {\n      \"data\": {\n        \"id\": \"1\",\n        \"type\": \"course\",\n        \"attributes\": {\n          \"title\": \"My Course!\"\n        },\n        \"relationships\": {\n          \"lessons\": {\n            \"data\": [\n              {\"id\": \"1\", \"type\": \"lessons\"},\n              {\"id\": \"2\", \"type\": \"lessons\"}\n            ],\n            \"links\": {\n              \"self\": \"/courses/1/relationships/lessons\",\n              \"related\": \"/courses/1/lessons\",\n              \"first\": \"/courses/1?include=lessons&included_page[lessons][limit]=10\",\n              \"next\": \"/courses/1?include=lessons&included_page[lessons][limit]=10&included_page[lessons][offset]=10\",\n              \"prev\": null,\n              \"last\": \"/courses/1?include=lessons&included_page[lessons][limit]=10&included_page[lessons][offset]=40\"\n            },\n            \"meta\": {\n              \"limit\": 10,\n              \"offset\": 0,\n              \"count\": 50\n            }\n          }\n        }\n      },\n      \"included\": [\n        {\n          \"id\": \"1\",\n          \"type\": \"lessons\",\n          \"attributes\": {\n            \"description\": \"Lessons about Elixir 1!\"\n          }\n        },\n        {\n          \"id\": \"2\",\n          \"type\": \"lessons\",\n          \"attributes\": {\n            \"description\": \"Lessons about Elixir 2!\"\n          }\n        }\n      ]\n    }\n\n\n####  Заключение и личный опыт\n\nПодводя итог, поделюсь опытом. На текущем проекте создание **_REST_** -эндпоинтов заняло всего 20–30 минут. Основное время ушло на кастомизацию отображения документации в **_Redoc_** — вот это оказалось довольно трудоёмким (_в отличие от генерации самого**API**_). Если решите использовать **_Redoc_** , будьте готовы вручную переписывать его блоки ради красивого вывода ответов и примеров.\n\nВ остальном инструмент показал себя отлично: великолепная гибкость, кастомизируемая обработка ошибок, тонкое управление доступом к эндпоинтам. Если цель — построить удобный и мощный **_REST API_** , **_AshJsonApi_** отличный выбор.\n\nㅤ\n\n\nㅤ\n\n\n##  🙂‍↕️ AshGraphQL\n\nС этой библиотекой возможны сложности: не так много людей вообще работали с **_GraphQL_**. Поэтому сначала дам вводную — освежить память или узнать, что это такое, — а потом посмотрим саму библиотеку.\n\n###  Что такое GraphQL\n\n**_GraphQL_** — это язык запросов для **_API_** и рантайм для их исполнения. Клиент сам описывает, какие данные ему нужны, а сервер возвращает ровно эту форму. Контрактом служит строго типизированная схема.\n\n####  Фундаментальные принципы\n\nВзаимодействие идёт через единый **_endpoint_** (_обычно`POST /graphql`_). Логика строится вокруг графа типов. Запрос имеет иерархическую структуру, и ответ сервера в точности её повторяет. Это решает проблемы **_over-fetching_** и **_under-fetching_**.\n\n####  Виды операций\n\n  * **Query** — операции на чтение (идемпотентны).\n  * **Mutation** — операции на изменение данных (выполняются последовательно).\n  * **Subscription** — подписка на поток событий в реальном времени (через _WebSocket_).\n\n\n\n####  Схема и резолверы\n\nЦентральный элемент — схема, описываемая на **_SDL_**. Она служит источником истины и позволяет проводить интроспекцию. Каждое поле сопоставляется с резолвером — функцией, возвращающей значение. Сервер обходит дерево запроса и вызывает резолверы для каждого поля.\n\n####  Сложности\n\n  * **Проблема N+1.** Резолверы работают независимо, и без оптимизации (батчинга) это приводит к множеству запросов к базе\n  * **Кэширование.** Стандартные **_HTTP_** -механизмы (**_CDN_** , **_ETag_**) не работают, так как всё идёт через **_POST_**. Требуется нормализованный кэш на клиенте\n  * **Безопасность.** Клиент может отправить глубоко вложенный запрос, перегружающий базу. Нужны ограничение глубины и анализ сложности\n  * **Ошибки.** Сервер почти всегда возвращает статус `200`, а сами ошибки помещаются в массив `errors` рядом с частичными данными. Это ломает мониторинг, основанный на кодах состояния **_HTTP_**\n  * **Инфраструктура.** Загрузка файлов, **_rate limiting_** и наблюдаемость требуют отдельного инфраструктурного слоя\n\n\n\n####  Где эффективен\n\nТехнология оправдывает себя при множестве разнообразных клиентов со сложными требованиями к данным. Для одного клиента с простыми ресурсами проще взять **_REST_**. Для внутреннего межсервисного взаимодействия с жёсткими контрактами и минимальной задержкой лучше подходит **_gRPC_**.\n\n###  Что представляет собой AshGraphQL\n\nНесмотря на то что **_GraphQL_** пока уступает в популярности другим подходам, его интеграция с **_Ash_** сделана очень качественно. Технологию легко внедрить в проекты на базе **_Ash_**.\n\nДля начала добавьте библиотеку в проект:\n\n\n\n    mix igniter.install ash_graphql\n\n\nЗатем в модуле схемы укажите используемые домены:\n\n\n\n    use AshGraphql, domains: [Your.Domain1, Your.Domain2]\n\n\n###  Настройка ресурсов и доменов\n\nЧтобы ресурс стал доступен через **_GraphQL_** , подключите расширение `AshGraphql.Resource` и опишите базовый блок конфигурации:\n\n\n\n    defmodule CourseHub.Courses.Lesson do\n      use Ash.Resource,\n        extensions: [\n          AshGraphql.Resource\n        ]\n\n      graphql do\n        type :lesson\n      end\n\n      # ... остальная логика ресурса\n    end\n\n\nВ модуле домена определите операции (ручки) через расширение `AshGraphql.Domain`:\n\n\n\n    defmodule CourseHub.Courses do\n      use Ash.Domain,\n        extensions: [\n          AshGraphql.Domain\n        ]\n\n      graphql do\n        queries do\n          get CourseHub.Courses.Lesson, :get_lesson, :read\n          read_one CourseHub.Courses.Lesson, :most_popular_lesson, :most_popular\n          list CourseHub.Courses.Lesson, :list_lessons, :read\n        end\n\n        mutations do\n          create CourseHub.Courses.Lesson, :create_lesson, :create\n          update CourseHub.Courses.Lesson, :update_lesson, :update\n          destroy CourseHub.Courses.Lesson, :destroy_lesson, :destroy\n        end\n      end\n    end\n\n\nОписывать эти ручки можно не только в модуле домена, но и прямо внутри ресурса.\n\nКроме того, вы можете создавать собственные действия для кастомных ответов. Пример:\n\n\n\n    graphql do\n      type :lesson\n\n      queries do\n        action :random_lesson, :random_lesson\n      end\n    end\n\n    actions do\n      action :random_lesson, {:array, :struct} do\n        constraints items: [instance_of: __MODULE__]\n\n        run fn _input, _context ->\n          # возвращаем список структур, как объявлено в типе действия\n          Ash.read(__MODULE__)\n        end\n      end\n    end\n\n\n####  Генерация схемы SDL\n\nКак и в **_AshJsonApi_** (_где**Swagger** генерируется автоматически после правок_), схема **_GraphQL_** обновляется схожим образом. Пропишите путь для сохранения в `use AshGraphql` через `generate_sdl_file:`. Файл будет автоматически генерироваться при каждом запуске `mix ash.codegen` (если включён `auto_generate_sdl_file?: true`):\n\n\n\n    use AshGraphql,\n      domains: [Domain1, Domain2],\n      generate_sdl_file: \"priv/schema.graphql\",\n      auto_generate_sdl_file?: true\n\n\n####  Создание подписок (Subscriptions)\n\nМожно настроить получение данных в реальном времени через механизм подписок. Для этого используется конфигурация **_Absinthe_** :\n\n\n\n    # в файле схемы Absinthe\n    subscription do\n      field :field, :type_name do\n        config(fn\n          _args, %{context: %{current_user: %{id: user_id}}} ->\n            {:ok, topic: user_id, context_id: \"user/#{user_id}\"}\n\n          _args, _context ->\n            {:error, :unauthorized}\n        end)\n\n        resolve(fn args, _, resolution ->\n          AshGraphql.Subscription.query_for_subscription(\n            YourResource,\n            YourDomain,\n            resolution\n          )\n          |> Ash.Query.filter(id == ^args.id)\n          |> Ash.read(actor: resolution.context.current_user)\n        end)\n      end\n    end\n\n\n####  Мониторинг\n\nДля глубокого погружения рекомендую официальное руководство по мониторингу Ash. Здесь кратко опишем механизмы трассировки и события телеметрии, которые становятся доступны с этим расширением.\n\nМожно определить собственный трассировщик на уровне домена — его настройки будут иметь приоритет над глобальным параметром `config :ash, :tracer`:\n\n\n\n    graphql do\n      tracer MyApp.Tracer\n    end\n\n\n####  Трассировка (Traces)\n\nОперации резолверов **_GraphQL_** и базовых пакетных загрузчиков данных оборачиваются в диапазоны (**_spans_**) с соответствующими именами. Для удобства к ним добавляется метаданное `source: :graphql`, что позволяет фильтровать или комментировать эти данные в системе мониторинга.\n\n####  Телеметрия (Telemetry)\n\nМодуль **_AshGraphql_** публикует события телеметрии, каждое из которых сопровождается суффиксами `:start` и `:stop`. События начала (`:start`) содержат измерение `system_time`, а события завершения (`:stop`) — `system_time` и длительность выполнения `duration`. Все временные показатели представлены в нативных единицах времени.\n\nСписок генерируемых событий:\n\n  * **`[:ash,<имя_домена>, :gql_mutation]`** — выполнение мутации. Для детализации используйте метаданные `resource_short_name` и `mutation` (или `action`)\n  * **`[:ash,<имя_домена>, :gql_query]`** — выполнение запроса. Для детализации используйте метаданные `resource_short_name` и `query` (или `action`)\n  * **`[:ash,<имя_домена>, :gql_relationship]`** — разрешение связи. Для детализации используйте метаданные `resource_short_name` и `relationship`\n  * **`[:ash,<имя_домена>, :gql_calculation]`** — разрешение вычисления. Для детализации используйте метаданные `resource_short_name` и `calculation`\n  * **`[:ash,<имя_домена>, :gql_relationship_batch]`** — пакетное разрешение связей загрузчиком данных. Для детализации используйте метаданные `resource_short_name` и `relationship`\n  * **`[:ash,<имя_домена>, :gql_calculation_batch]`** — пакетное разрешение вычислений загрузчиком данных. Для детализации используйте метаданные `resource_short_name` и `calculation`\n\n\n\n###  Отзыв\n\nЧестно говоря, я нечасто прибегал к такому формату взаимодействия, но из того немногого опыта, что у меня есть, скажу одно — это действительно удобно.\n\nㅤ\n\n\nㅤ\n\n\n##  😳 AshTypescript — скрытая жемчужина\n\nМы уже познакомились с **_AshJsonApi_** и **_AshGraphQL_** , и это был полезный опыт. Эти два подхода — своего рода индустриальный стандарт веб-разработки, и особых сюрпризов для опытных специалистов в них нет. Но у **_Ash_** есть «козырная карта» — AshTypescript.\n\nПредставьте, что вы хотите взаимодействовать с фронтендом с типобезопасностью уровня **_gRPC_** , сохраняя структуру эндпоинтов как в **_AshJsonApi_** и гибкость работы с данными как в **_AshGraphQL_**. Именно это и решает **_AshTypescript_**.\n\n###  Принцип работы\n\n  1. **Определение структуры.** Вы описываете домен и ресурсы, указывая нужные действия (**_actions_**) и атрибуты\n  2. **Подключение расширений.** В ресурс добавляется `AshTypescript.Resource`, а в домен — `AshTypescript.Rpc`\n  3. **Настройка ресурса.** В блоке `typescript` внутри ресурса задаётся его имя для генерации\n\n\n\n\n    defmodule CourseHub.Courses.Lesson do\n      use Ash.Resource,\n        domain: CourseHub.Courses,\n        extensions: [AshTypescript.Resource] # Подключаем расширение\n\n      # Задаём имя типа для сгенерированного RPC-файла\n      typescript do\n        type_name \"Lesson\"\n      end\n\n      # Стандартные CRUD-действия\n      actions do\n        defaults [:read, :create, :update, :destroy]\n      end\n\n      # Описываем атрибуты ресурса\n      attributes do\n        uuid_primary_key :id\n        attribute :title, :string, allow_nil?: false\n        attribute :description, :string, default: \"Какой-то текст!\"\n      end\n\n      # Описываем связь с Course\n      relationships do\n        belongs_to :course, CourseHub.Courses.Course\n      end\n    end\n\n\n  1. **Привязка RPC-методов.** В модуле домена вы определяете, какие методы будут доступны фронтенду, и связываете их с действиями ресурса.\n\n\n\n\n    defmodule CourseHub.Courses do\n      use Ash.Domain, extensions: [AshTypescript.Rpc]\n\n      # Публичные RPC-методы для фронтенда\n      typescript_rpc do\n        resource CourseHub.Courses.Lesson do\n          rpc_action :list_lessons, :read\n          rpc_action :create_lesson, :create\n          rpc_action :get_lesson, :get\n          rpc_action :delete_lesson, :destroy\n          rpc_action :update_lesson, :update\n        end\n      end\n    end\n\n\n  1. **Генерация кода.** Запустив `mix ash_typescript.codegen` (_или общую`mix ash.codegen`_), вы получите сгенерированные файлы по пути из конфигурации. Настройки лежат в `config/config.exs`:\n\n\n\n\n    config :ash_typescript,\n      ash_domains: [\n        Backend.CourseHub\n      ],\n      output_file: \"assets/js/ash_rpc.ts\", # Путь для сохранения сгенерированного кода\n      run_endpoint: \"/rpc/run\",            # Эндпоинт для отправки запросов\n      validate_endpoint: \"/rpc/validate\",  # Эндпоинт для валидации данных по схеме\n      input_field_formatter: :camel_case,  # Формат имён полей на входе\n      output_field_formatter: :camel_case, # Формат имён полей на выходе\n      require_tenant_parameters: false,    # Мультитенантность\n      generate_zod_schemas: true,          # Генерация схем для Zod\n      generate_phx_channel_rpc_actions: false,\n      generate_validation_functions: true,\n      zod_import_path: \"zod\",\n      zod_schema_suffix: \"ZodSchema\",\n      phoenix_import_path: \"phoenix\"\n\n\n> **Zod** — библиотека для описания и проверки структуры данных. Она позволяет создавать схемы и автоматически валидировать по ним входящие данные.\n>\n> **Ключевые преимущества Zod:**\n>\n>   * **Ориентированность на TypeScript** — автоматически выводит типы\n>   * Интуитивно понятный и легко читаемый API\n>   * Поддержка сложных правил валидации (_объединения типов, пересечения, уточнения_)\n>   * Высокая производительность и минимальный вес\n>\n\n\nНачиная с новых версий, AshTypescript генерирует не один монолитный файл, а несколько: помимо `ash_rpc.ts` появляются общие типы (`ash_types.ts`) и схемы **_Zod_** (`ash_zod.ts`), которые импортируются из общего места.\n\nВ итоге вам остаётся лишь передать сгенерированные файлы на фронтенд. Разработчики клиента просто вызывают методы, например `listLessons()`, без ручного описания **_API_**. Это делает интеграцию невероятно простой и удобной.\n\n###  Механизмы сортировки, фильтрации и проекции полей\n\nВ стандартной серверной разработке приходится вручную конструировать **_SQL_** -запросы для сортировки, фильтрации и выборки, чтобы избежать избыточной передачи данных. В **_AshTypescript_** реализованы те же возможности, что и в **_AshJsonApi_** , — гибко и типобезопасно:\n\n\n\n    // CREATE\n    const createLessonResponse = await createLesson({\n      fields: [\"id\", \"title\"],\n      input: { title: \"Elixir lesson about Ash\", priority: \"very high!\" },\n      headers\n    });\n\n    if (!createLessonResponse.success) {\n      console.error(\"Create failed:\", createLessonResponse.errors);\n      return;\n    }\n\n    const lessonId = createLessonResponse.data.id;\n\n    // READ (single)\n    const getLesson = await getLesson({\n      fields: [\"id\", \"title\", \"description\", { course: [\"name\"] }],\n      input: { id: lessonId },\n      headers\n    });\n\n    // READ (list)\n    const lessons = await listLessons({\n      fields: [\"id\", \"title\"],\n      headers\n    });\n\n    // UPDATE\n    const updateLessonResult = await updateLessson({\n      fields: [\"id\", \"title\"],\n      identity: lessonId,\n      input: { title: \"Yoo chill im just a vessel!\" },\n      headers\n    });\n\n    // DELETE\n    const deleteLessonResponse = await deleteLesson({\n      identity: lessonId,\n      headers\n    });\n\n\n###  Преимущества использования AshTypescript\n\n  1. **Гарантия целостности контракта.** Не нужно беспокоиться о рассинхроне интерфейса, как это бывает с «ручным» **_gRPC_**. Единственный источник истины — генератор _.ts_ -файлов\n  2. **Автоматизация создания API.** Отпадает ручное написание методов и эндпоинтов. Достаточно описать ресурс и `typescript_rpc` в домене и дать методам уникальные имена\n  3. **Минимизация шаблонного кода.** Встроенные инструменты для сортировки, фильтрации и выборки полей (_**select** /проекции_) делают работу бесшовной\n  4. **Простая интеграция с другими командами.** Сгенерированные файлы (**_ash_rpc.ts_ , _ash_types.ts_ , _ash_zod.ts_**) можно передать коллегам — они начнут работу немедленно, не тратя время на изучение документации\n\n\n\n###  Впечатления\n\nУзнав об этом способе организации взаимодействия между сервером и клиентом, я вновь испытал энтузиазм к программированию. Ручное написание эндпоинтов монотонно, а описание схем для **_GraphQL_** отнимает много времени. Декларативное же описание **_actions_** на бэкенде и простой вызов методов на фронтенде — это быстро и удобно. Такой подход убирает до 80% лишнего кода и многократно ускоряет разработку.\n\nㅤ\n\n\nㅤ\n\n\n##  🧮 Сравнение трёх подходов\n\nУ всех трёх способов один фундамент — ваши **_actions_**. Меняется только «витрина», через которую логика выходит наружу. Отсюда и главный вывод: выбор способа не запирает вас в нём навсегда — при необходимости можно добавить второй контракт поверх той же логики.\n\nКритерий | AshJsonApi (REST / JSON:API) | AshGraphQL | AshTypescript\n---|---|---|---\n**Тип контракта** | REST по стандарту JSON:API | GraphQL-схема (SDL) | Сгенерированные `.ts`-файлы (RPC)\n**Источник истины** | ваши _actions_ + автоген OpenAPI | ваши _actions_ + автоген SDL | ваши _actions_ + автоген TypeScript\n**Как клиент задаёт запрос** | query-параметры (`filter`, `sort`, `include`) | GraphQL-запрос с деревом полей | типизированные вызовы (`fields`, `filter`, `sort`)\n**Гибкость выборки** | высокая | максимальная | высокая\n**Индустриальный стандарт** | да, повсеместно | да, но нишевый | нет, специфичен для стека Ash + TS\n**Типобезопасность на фронте** | через кодоген из OpenAPI | через кодоген из схемы | нативная, из коробки\n**Реалтайм** | нет из коробки | да (subscriptions) | да (Phoenix channels)\n**HTTP-кэширование** | работает (GET) | затруднено (POST) | зависит от реализации\n**Порог входа на фронте** | низкий | средний/высокий | низкий (для TypeScript-команд)\n**Идеально для** | публичные и внешние API | много разных клиентов со сложными запросами | full-stack Elixir + TypeScript\n\n**Как выбирать на практике:**\n\n  * **AshJsonApi** — когда нужен привычный **_REST_** , публичный или внешний **_API_** , кэширование на уровне **_HTTP_** и максимальная совместимость с любыми клиентами. Самый «нейтральный» выбор\n  * **AshGraphQL** — когда клиентов много и у них разные, часто вложенные требования к данным, а под-/пере-выборка (**_over/under-fetching_**) реально мешает. Готовьтесь платить за это батчингом против N+1, ограничением сложности запросов и клиентским кэшем\n  * **AshTypescript** — когда и бэкенд, и фронтенд ваши, и фронтенд на **_TypeScript_**. Даёт сквозную типобезопасность без ручного контракта: переименовали поле в **_Elixir_** — сразу получили ошибку компиляции в **_TS_**\n\n\n\nЕсли сомневаетесь и клиент один и относительно простой — начните с **_AshJsonApi_**. Если позже понадобится **_GraphQL_** или типобезопасный **_RPC_** , вы просто добавите ещё один контракт поверх тех же **_actions_** , не переписывая логику.\n\nㅤ\n\n\nㅤ\n\n\n##  😴 Заключение\n\nМир **_Ash_** даёт не просто удобный формат работы с БД и отсутствие бойлерплейта — он позволяет делать приложения быстро, качественно и с удовольствием. На полное освоение этих методов уйдёт чуть больше времени, но оно окупится не раз и здорово поможет в ваших проектах. Успехов!\n\nㅤ\n\n\nㅤ\n\n\n##  🥴 От автора\n\nСпасибо большое за интерес к этой статье! Надеюсь, она помогла разобраться, что представляет собой **_AshJsonApi_** , **_AshGraphQL_** , **_AshTypescript_** и зачем они используются!\n\nЕсли эта статья пришлась вам по душе и хочется ещё материалов такого плана, присоединяйтесь ко мне в моём телеграм-канале, где выкладываю обзоры книг, публикации по **_Elixir_** , переводы технической литературы и интересные новости!",
  "title": "Ash: способы взаимодействия — AshJsonApi, AshGraphQL и AshTypescript"
}