Angular: когда надо пилить приложение, а backend еще не готов

    Если вы занимаетесь frontend разработкой, то наверняка вам знакома следующая мизансцена: сроки по проекту стремительно сжимаются, ваше руководство, или заказчик, а-то и оба вместе желают увидеть глазами работающее приложение прямо сейчас, пусть и с ненастоящими данными. При этом back, возможно, и есть, но именно api слой взаимодействия с front'ом отсутствует от слова совсем.

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

    Теперь попробую серьезно. С одной стороны ситуация нередкая, и решений может быть выбрано много.

    В голову приходило несколько вариантов решения:

    1. Захардкодить данные на уровне компонент
    2. Захардкодить данные на уровне resolver сервисов, приколотить их к нужным роутам
    3. Захардкодить данные на уровне сервисов поставщиков данных
    4. Запилить api, и, согласно оговоренным контрактам, возвращать захардкоженные данные

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

    1. Первый вариант отпал сразу — абсолютно неудобное непереиспользуемое решение, по мере развития проекта все придется переписывать.
    2. Данное решение могло иметь место, но опять таки структура проекта и логика работы компонент будет переписываться.
    3. Возможный вариант, можно даже возвращать заглушечные данные асинхронно, имитируя обращение к серверу, однако, как и в предыдущих вариантах решения проблемы, наши interceptor'ы (если они есть, а они есть) оставались бы не у дел, и выходит, что имитация работы с бэком становится неполной.
    4. Последний вариант казался весьма приемлемым, лишен проблем, которые есть у предыдущих вариантов, но писать хардкод в backend проект не хотелось просто из гигиенических соображений.

    В итоге был выбран иной вариант: поднять отдельный web сервер, который возвращал бы данные согласно роутам и контрактам, и настроить отдельную конфигурацию сборки и исполнения angular. Благо и то и другое оказалось сделать нетрудно.

    Для реализации mock сервера был выбран express.

    Давайте с него и начнем.

    Выбираем место где мы хотим писать код для moсk сервера, например в директория mock-server рядом с проектом ng.

    Далее нужно инициализировать проект и добавить пакет с express.

    npm init

    npm install --save express

    Далее добавим код, который нам будет возвращать данные. Создаем файл index.js, берем код из первого попавшегося tutorial.

    const express = require("express");
    const app = express();
    
    app.get("/url", (req, res, next) => {
      res.json(["Tony", "Lisa", "Michael", "Ginger", "Food"]);
    });
    
    app.listen(3000, () => {
      console.log("Server running on port 3000");
    });
    

    Запустим сервер

    node index.js

    Проверим с помощью postman:



    Все супер, сервер работает. Теперь давайте настроим один из роутов, так как будто мы запрашиваем данные из настоящего api. Допустим, нам нужен список всех книг, наполним книгами файл books.json

    [
      {
        "rn": 0,
        "id": "0",
        "name": "Jungle",
        "type": 0,
        "wells": 10042,
        "default": false,
        "hidden": false,
        "author": "Admin"
      },
      {
        "rn": 1,
        "id": "1",
        "name": "Main",
        "type": 1,
        "wells": 156,
        "default": true,
        "hidden": false,
        "author": "User"
      }
    ]

    И обновим файл приложения:

    const express = require("express");
    const app = express();
    
    app.get("/api/book/", (req, res, next) => {
      const books = require('./books');
      res.json(books);
    });
    
    app.listen(3000, () => {
      console.log("Server running on port 3000");
    });
    

    И проверим:



    Отлично.
    Теперь приступим к приложению angular.
    Добавим в файлы environments/environment*.ts конфигурацию, которая хранит адрес до бэка.
    environment.ts:

    export const environment = {
      production: false,
      backend: 'http://localhost:5000/'
    }
    

    environment.prod.ts

    export const environment = {
      production: true,
      backend: 'http://localhost:5000/'
    }
    

    В нормальном режиме, и в проде и в режиме разработки мы будем искать .net core api на 5000 порту, что и описано выше. Далее опишем конфигурацию для временного бэка
    environment.mock.ts

    export const environment = {
      production: false,
      backend: 'http://localhost:3000/'
    }
    

    Здесь, как видно мы ищем api на 3000 порту, где мы будем запускать express.

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

    @Injectable()
    export class RequestInterceptor implements HttpInterceptor {
      baseUrl: string;
    
      constructor() {
        this.baseUrl = environment.backend;
      }
    
      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(this.modifyRequest(req));
      }
    
      private modifyRequest = (req: HttpRequest<any>): HttpRequest<any> => {
        if (req.url.startsWith('api/')) {
          const url = this.baseUrl;
          req = req.clone({
            url: url + req.url
          });
        }
        return req;
      }
    }
    

    Осталось настроить новую конфигурацию сборки и запуска приложения для работы с mock сервером.

    Для этого нам надо немного подправить angular.json.

    В секции вашего проекта, architect/build/configurations добавим новую конфигурацию сборки mock, и опишем замену файлов environment для этой конфигурации. Также для для режима serve создадим конфигурацию mock и укажем нужный вариант сборки

    {
      "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
      "version": 1,
      "newProjectRoot": "projects",
      "projects": {
        "your-project": {
          /*****/
          "architect": {
            "build": {
              /*****/
              "configurations": {
                "production": {
                  /*****/
                },
                "mock": {
                  "fileReplacements": [
                    {
                      "replace": "src/environments/environment.ts",
                      "with": "src/environments/environment.mock.ts"
                    }
                  ]
                }
              }
            },
            "serve": {
              "builder": "@angular-devkit/build-angular:dev-server",
              "options": {
                "browserTarget": "your-project:build"
              },
              "configurations": {
                "production": {
                  "browserTarget": "your-project:build:production"
                },
                "mock": {
                  "browserTarget": "your-project:build:mock"
                }
              }
            }
          },
          /*****/
        }
      }
    }
    

    Вот и все, теперь осталось запустить проект в нужной конфигурации

    
    ng serve --configuration=mock
    

    и проверить куда улетают обращения к бэку:



    Все отлично.

    На самом деле данная конструкция нам еще очень поможет, когда мы будем прикручивать к проекту интеграционные и e2e тесты. Об этом постараюсь написать в ближайшее время.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 18

      0
      Спасибо! Полезная статья! Можно сервер собрать в докер образ, и тогда развертка будет делом пары минут.
        0
        Да, у нас нынче весь деплой на новых проектах через докер идет, тебе ли не знать)
        +1
        Сами данные можно генерировать с помощью fake (https://github.com/marak/Faker.js/)
        Так же расстраивает, чтодля ангуляра нет пока ничего похожего как Mirage для ember (https://github.com/samselikoff/ember-cli-mirage/)
          +1
          Скажите, а вы пробовали вот это решение?
          github.com/typicode/json-server

            0
            нет, это решение не пробовал, вроде слышал о нем. Оно полностью соответствует поставленной задаче, спасибо. Посмотрю размеры, я так думаю json-server окажется более легковесным, тогда можно перейти на него.
              0
              JSON-Server еще можно поднимать в CI-джобе для интеграционного тестирования вашего приложения. Благо, у него куча настроек.
              0
              Мы пробовали его использовать. На старте было хорошо, когда начали появляться хитрые кейсы, POST запросы с бизнес логикой, начали мокать эррор хэндлинг и т.д., поддержка стала очень дорогой и всё перевели на экспресс. Я бы советовал джейсон-сервер только на самом старте и только для crud операций, либо сферического RESTfull апа.
              +1
              Имеется вот такой пакет github.com/angular/in-memory-web-api

              Попробуйте, может то что нужно.
                0
                спасибо, посмотрю
                0
                У ангуляр-кли есть конфигуратор проксей для локальной разработки. Я бы посоветовал использовать его вместо самописного интерсептора, который замусоривает код. Пример использования (все ваши кейсы он отлично покроет):
                github.com/angular/angular-cli/blob/master/docs/documentation/stories/proxy.md
                  0
                  Запилить api, и, согласно оговоренным контрактам, возвращать захардкоженные данные

                  Эээээ… А как насчёт


                  • Запилить тестовый api и прошить линки ссылок в приложении на конфиг, который поменять, когда выйдет "боевой" api, не?
                    0
                    Если я вас правильно понял, то так и получилось в итоге. Но фейковый апи хотелось иметь в доступе, если боевой готов не полностью, оставалась возможность продолжать разрабатывать фронт с опережением. Во вторых тесты гонять на нем удобнее, поэтому нужна была параллельная конфигурация на фейк апи, а не просто замена линки одной на другую.
                      0

                      Если подключён аналог репозитория с DI только не на базу данных, а на ссылки api то вам боевой — вообще не нужен. Как собственно и должно быть по фен-шую разработки :). Если вам для вашей разработки нужны "реальные данные" — то что-то сдохло у вас в Датском королевстве и от этого надо отходить.
                      Вам же главное знать, что api вызывается и возвращает результаты, котоыре вы можете отработать.

                    0
                    Делать require внутри функции обработчика это самая большая жесть которую только можно сделать в NodeJS. Лучше обьявите books в начало модуля.

                    const books = require('./books');
                      0
                      Хотелось бы увидеть результаты с инт-тестами. Очень интересно увидеть ваш подход. Конечно решение на докерах будет очень и очень…
                        0
                        Постараюсь в ближайшее время подготовить статью на эту тему, к сожалению, пока работа не дает отвлечься на подготовку материала.
                          0
                          Спасибо буду искренне благодарен.
                        0

                        Вариант с express, и даже с json-server не очень хорош, поскольку тесты для CI не напишешь, для демонстрации работоспособности невозможно воспользоватся онлайн редакторами, типа StackBlitz или codepen и т.д., а для демонстрации даже коллеге со своей команды (например, тестировщику), нужно усложнение в виде контейнера какого-то...


                        В этом плане, конечно же, лучше использовать in-memory-web-api, но этот модуль подойдет только для совсем простых API.


                        Есть еще вариант с @ng-stack/api-mock (я являюсь его автором), но он еще сырой...

                        Only users with full accounts can post comments. Log in, please.