Серия Backend Development Practice — 0-я итерация для разработчиков

задняя часть

В ThoughtWorks я создал множество программных проектов с нуля, включая базовую структуру кода, инфраструктуру непрерывной интеграции и т. д., которые часто называют задачами, которые необходимо выполнить на «0-й итерации» гибкой разработки. Однако, когда проект выполняется в течение определенного периода времени, а затем я оглядываюсь назад, я всегда нахожу какие-то недостатки, либо классификация тестов плохо классифицирована, либо базовая структура кодирования плохо продумана.

Кроме того, я также соприкасаюсь со многими существующими проектами в своей работе, как внутри компании, так и за ее пределами, и меня не устраивает практика кодирования большинства проектов. Например, когда я присоединился к новому проекту, я посоветовался с 3 коллегами до и после запуска проекта локально; например, в другом проекте я обнаружил, что соглашение об именовании классов Java, соответствующее внешнему запросу, не было единообразным. Те, с суффиксом Request, также имеют суффикс Command.

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

Основываясь на вышеизложенном, я надеюсь отсортировать набор общедоступных шаблонов проектов, чтобы включить как можно больше ежедневных потребностей в разработке, сократить повторяющуюся работу разработчиков и предоставить некоторые передовые практики. Для back-end разработки я выбрал Spring Boot, который на данный момент широко используется в индустрии, исходя из этого я разобрал набор общедоступных и базовых практик, объединив собственный опыт и лучшие практики других проектов, я Резюме Эта статья опубликована для разработчиков.

В этой статье в качестве примера используется простая система заказов электронной коммерции. Исходный код доступен по адресу:

GitHub.com/oh-commerce-…

Используемые технологические стеки в основном включают: Spring Boot, Gradle, MySQL, Junit 5, Rest Assured, Docker и т. д.

Шаг 1: Начните с написания README

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

  • Введение в проект: кратко опишите бизнес-функции, реализуемые проектом, в одном-двух предложениях;
  • Технический выбор: перечислите техническую стопку проектов, включая язык, рамки и промежуточное программное обеспечение и т. Д.;
  • Локальная сборка: перечислите команды инструментов, используемые в процессе локальной разработки;
  • Модель предметной области: основные понятия предметной области, такие как «Заказ», «Продукт» и т. д., для примера системы электронной коммерции;
  • Стратегия тестирования: как классифицировать автоматизированные тесты, какие из них должны быть написаны тестами, а какие не обязательно писать тесты;
  • Техническая архитектура: схема технической архитектуры;
  • Архитектура развертывания: схема архитектуры развертывания;
  • Внешние зависимости: внешние интеграторы, от которых зависит проект при запуске, например, система заказов будет зависеть от системы членства;
  • Информация об окружении: методы доступа к каждому окружению, подключение к базе данных и т. д.;
  • Практика кодирования: унифицированная практика кодирования, такая как принципы обработки исключений, инкапсуляция страниц и т. д.;
  • FAQ: ответы на часто задаваемые вопросы во время разработки.

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

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

Локальная сборка в один клик

Чтобы избежать смущения, связанного с тем, что «локальная конструкция прошла успешно после консультации с 3 коллегами», упомянутого выше, чтобы уменьшить ручную работу «ленивых» программистов и чтобы обеспечить последовательный опыт разработки для всех разработчиков, мы хотим делать все одной командой. Здесь я суммировал следующие команды для разных сценариев:

  • Создайте проект IDE:idea.sh, создать файл проекта IntelliJ и автоматически открыть IntelliJ
  • Запустить локально:run.sh, запустить проект локально, автоматически запустить локальную базу данных, прослушивать порт отладки 5005
  • Локальная сборка:local-build.sh, код может быть отправлен только в том случае, если локальная сборка прошла успешно.

Вышеупомянутые три команды могут в основном удовлетворить ежедневные потребности в разработке.В настоящее время процесс разработки для новичков выглядит примерно следующим образом:

  1. вытащить код;
  2. бегатьidea.sh, который автоматически открывает IntelliJ;
  3. Писать код, включая бизнес-код и автоматизированные тесты;
  4. бегатьrun.sh, выполнить локальную отладку или необходимое ручное тестирование (этот шаг необязателен);
  5. бегатьlocal-build.sh, чтобы завершить локальную сборку;
  6. Потяните код еще раз, чтобы убедиться,local-build.shУспех, отправьте код.

На самом деле содержимое этих командных сценариев очень простое, напримерrun.shСодержимое файла:

#!/usr/bin/env bash
./gradlew clean bootRun

Тем не менее, эта явная команда может уменьшить страх новичков, потому что им нужно знать только запуск этих 3 команд, чтобы заниматься разработкой. Также небольшая деталь: местная постройкаlocal-build.shКоманду можно было бы переименовать во что-то более простое.build.sh, но когда мы используем клавишу Tab для автозаполнения в командной строке, мы обнаружим, что автозаполнение прибылоbuildкаталог вместоbuild.shкомандный, неудобный, так называемыйlocal-build.sh. Детали небольшие, но они воплощают цель, то есть мы надеемся дать разработчикам минимальный опыт разработки.Я называю эти, казалось бы, незначительные вещи «гуманной заботой» о программистах.

Структура каталогов

Сторонники MavenСтруктура каталоговВ настоящее время Gradle является отраслевым стандартом де-факто и по умолчанию также использует структуру каталогов Maven, чего достаточно для большинства проектов. В дополнение к коду Java в проекте существуют другие типы файлов, такие как конфигурации плагинов Gradle, скрипты инструментов и конфигурации развертывания. В любом случае, принцип структуры каталогов проекта — быть простым и организованным, не добавлять произвольно лишние папки, а также нуждаться в своевременном рефакторинге.

В примере проекта всего 2 папки на верхнем уровне, одна для размещения исходного кода Java и конфигурации проектаsrcпапка, в другой находятся все конфигурации Gradlegradleпапку, кроме того, для удобства разработчиков поместите три часто используемых скрипта, упомянутых выше, прямо в корневой каталог:

└── order-backend
    ├── gradle // 文件夹,用于放置所有Gradle配置
    ├── src // 文件夹,Java源代码
    ├── idea.sh //生成IntelliJ工程
    ├── local-build.sh // 提交之前的本地构建
    └── run.sh // 本地运行

дляgradleНапример, мы намеренно поместили скрипт плагина Gradle вместе с конфигурацией плагина, такой как Checkstyle:

├── gradle
│   ├── checkstyle
│   │   ├── checkstyle.gradle
│   │   └── checkstyle.xml

На самом деле, по умолчанию плагин Checkstyle запускается из корневого каталога проекта.configпоиск в каталогеcheckstyle.xmlфайл конфигурации, но, с одной стороны, добавляет лишние папки, а с другой стороны, средства, относящиеся к плагину, разбросаны по разным местам, что нарушает принцип связности в широком смысле.

На основе коммерческого субподряда

В первые годы метод субподряда Java обычно основывался на технологии.Например, существовали пакеты контроллеров, пакеты услуг и пакеты инфраструктуры, которые были на одном уровне с пакетом предметной области. Этот метод в настоящее время не соблюдается в отрасли, но сначала он должен основываться на субподряде бизнеса. Например, в примере проекта заказа есть два важных предметных объекта.Orderа такжеProduct(существуетDDDназывается вСовокупный корень), все предприятия вращаются вокруг них, поэтому создайте пакет заказа и пакет продукта соответственно, а затем создайте подпакеты, связанные с ними, в пакете соответственно. Пакет заказов на данный момент выглядит следующим образом:

├── order
│   ├── OrderApplicationService.java
│   ├── OrderController.java
│   ├── OrderNotFoundException.java
│   ├── OrderRepository.java
│   ├── OrderService.java
│   └── model
│       ├── Order.java
│       ├── OrderFactory.java
│       ├── OrderId.java
│       ├── OrderItem.java
│       └── OrderStatus.java

Как видите, мы разместили его прямо под пакетом заказа.OrderControllerа такжеOrderRepositoryEquisical, и нет необходимости выделять отдельный подпакет для этих классов. Для модели предметной области ORDER из-за нескольких объектов они возвращаются в пакет модели на основе принципа внутреннего сбора. Но это не обязательно, если бизнес достаточно простой, мы можем даже поместить все классы прямо под бизнес-пакет, пакет продукта такой:

└── product
    ├── Product.java
    ├── ProductApplicationService.java
    ├── ProductController.java
    ├── ProductId.java
    └── ProductRepository.java

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

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

└── common
    ├── configuration
    ├── exception
    ├── loggin
    └── utils

Автоматическая классификация тестов

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

Кроме того, в программе есть некоторый код фреймворка, либо код технического фреймворка, такой как Controller, либо код, основанный на определенном архитектурном стиле (например, в практике DDD).ApplicationService), с одной стороны, эти коды не содержат бизнес-логики, с другой — представляют собой тонкий слой абстракции (т. е. реализация относительно проста), и покрывать их юнит-тестами не нужно, поэтому Авторская точка зрения заключается в том, что нельзя писать для этого отдельный блок теста. Кроме того, некоторые важные коды компонентов в программе, такие как репозиторий или распределенные блокировки, которые обращаются к базе данных, на самом деле «необнаружимы» с помощью модульного тестирования, а использование тестирования API кажется неразумным с точки зрения логики классификации.Мы можем специально создать тип теста называется компонентным тестом.

На основании вышеизложенного мы можем сделать классификацию автоматизированного тестирования:

  • Модульный тест: основная модель домена, включая объекты домена (такие как класс Order), класс Factory, класс обслуживания домена и т. д.
  • Тестирование компонентов: классы, которые не подходят для написания модульных тестов, но должны быть протестированы, например классы репозитория, в некоторых проектах этот тип тестирования также называется интеграционным тестированием;
  • Тест API: смоделируйте клиента для тестирования каждого интерфейса API, необходимо запустить программу.

Gradle предоставляет только по умолчаниюsrc/test/javaКаталог используется для тестирования, а для вышеперечисленных 3-х типов тестов нам нужно их разделить для удобства управления (тоже проявление разделения обязанностей). Для этого тестовый код можно классифицировать по наборам SourceSet, предоставленным Gradle:

sourceSets {
    componentTest {
        compileClasspath += sourceSets.main.output + sourceSets.test.output
        runtimeClasspath += sourceSets.main.output + sourceSets.test.output
    }

    apiTest {
        compileClasspath += sourceSets.main.output + sourceSets.test.output
        runtimeClasspath += sourceSets.main.output + sourceSets.test.output
    }
}

На данный момент 3 типа тестов могут быть записаны в следующие каталоги:

  • модульный тест:src/test/java
  • Компонентный тест:src/componentTest/java
  • Тест API:src/apiTest/java

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

Стоит отметить, что поскольку для запуска программы необходимо тестирование компонентов и тестирование API, то есть локальная база данных должна быть подготовлена, мы используем Gradle’sdocker-composeплагин (илиджиб-плагин), Плагин автоматически запустит контейнер Docker перед запуском теста (например, MySQL):

apply plugin: 'docker-compose'


dockerCompose {
    useComposeFiles = ['docker/mysql/docker-compose.yml']
}

bootRun.dependsOn composeUp
componentTest.dependsOn composeUp
apiTest.dependsOn composeUp

Дополнительные сведения о конфигурации классификации тестов, такие какJaCoCoДля настройки тестового покрытия и т. д. см. пример кода проекта в этой статье. Читатели, незнакомые с Gradle, могут обратиться кСерия обучения Gradleстатья.

обработка журнала

При обработке логов, помимо выполнения базовой настройки, необходимо учитывать 2 момента:

  • Идентификатор запроса добавлен в журнал для облегчения отслеживания ссылки. В ходе обработки запроса иногда выводится несколько журналов, если каждый журнал имеет общий идентификатор запроса, это будет более удобно при отслеживании журнала. На этом этапе вы можете использовать собственный Logback в функции MDC (Mapped Diagnostic Context), создав RequestIdMdcFilter:
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        //request id in header may come from Gateway, eg. Nginx
        String headerRequestId = request.getHeader(HEADER_X_REQUEST_ID);
        MDC.put(REQUEST_ID, isNullOrEmpty(headerRequestId) ? newUuid() : headerRequestId);
        try {
            filterChain.doFilter(request, response);
        } finally {
            clearMdc();
        }
    }
  • Централизованное управление журналом. В сценарии развертывания нескольких узлов журналы каждого узла разбросаны. По этой причине такие инструменты, как elk, могут быть введены в равномерное вывод журналов для Elasticsearch. Пример проекта в этой статье использует Redisappender к выводам журналы для логизации:
<appender name="REDIS" class="com.cwbase.logback.RedisAppender">
    <tags>ecommerce-order-backend-${ACTIVE_PROFILE}</tags>
    <host>elk.yourdomain.com</host>
    <port>6379</port>
    <password>whatever</password>
    <key>ecommerce-ordder-log</key>
    <mdc>true</mdc>
    <type>redis</type>
</appender>

Конечно, существует множество решений для унифицированного ведения журналов, таких как Splunk и Graylog.

Обработка исключений

При разработке фреймворка для обработки исключений необходимо учитывать следующие моменты:

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

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

Пример проекта для этой статьи использует иерархические исключения, все из которых наследуются от AppException:

public abstract class AppException extends RuntimeException {
    private final ErrorCode code;
    private final Map<String, Object> data = newHashMap();
}

здесь,ErrorCodeПеречисление содержит уникальный идентификатор исключения, код состояния HTTP и сообщение об ошибке; а такжеdataПоля представляют контекстную информацию для каждого исключения.

В примере системы выдается исключение, когда заказ не найден:

public class OrderNotFoundException extends AppException {
    public OrderNotFoundException(OrderId orderId) {
        super(ErrorCode.ORDER_NOT_FOUND, ImmutableMap.of("orderId", orderId.toString()));
    }
}

При возврате исключения клиенту используйте класс ErrorDetail для унификации формата исключения:

public final class ErrorDetail {
    private final ErrorCode code;
    private final int status;
    private final String message;
    private final String path;
    private final Instant timestamp;
    private final Map<String, Object> data = newHashMap();
}

Окончательные данные, возвращаемые клиенту:

{
  requestId: "d008ef46bb4f4cf19c9081ad50df33bd",
  error: {
    code: "ORDER_NOT_FOUND",
    status: 404,
    message: "没有找到订单",
    path: "/order",
    timestamp: 1555031270087,
    data: {
      orderId: "123456789"
    }
  }
}

можно увидеть,ORDER_NOT_FOUNDа такжеdataСтруктура данных в корреспонденции один к одному, то есть для клиента, если она найденаORDER_NOT_FOUND, то можно определить, чтоdataдолжен существовать вorderIdПоля, а затем полное точное структурированное распределение.

Фоновые задачи и распределенные блокировки

В дополнение к немедленному выполнению запроса клиента, в системе обычно есть некоторые запланированные рутинные задачи, такие как регулярная отправка электронных писем пользователям или создание отчетов с данными и т. д. Кроме того, иногда по замыслу мы будем выполнять асинхронную обработку запросов. На данный момент нам нужно построить инфраструктуру, связанную с фоновыми задачами. Spring изначально предоставляет механизмы обработки задач (TaskExecutor) и планирования задач (TaskSchedulor); в распределенных сценариях также необходимо ввестиРаспределенная блокировкаДля разрешения конфликтов параллелизма мы представляем облегченную структуру распределенных блокировок.ShedLock.

Включите конфигурацию задачи Spring следующим образом:

@Configuration
@EnableAsync
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(newScheduledThreadPool(10));
    }

    @Bean(destroyMethod = "shutdown")
    @Primary
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(10);
        executor.setTaskDecorator(new LogbackMdcTaskDecorator());
        executor.initialize();
        return executor;
    }

}

Затем настройте Shedlock:

@Configuration
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class DistributedLockConfiguration {

    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }


    @Bean
    public DistributedLockExecutor distributedLockExecutor(LockProvider lockProvider) {
        return new DistributedLockExecutor(lockProvider);
    }


}

Реализовать фоновую обработку задач:

    @Scheduled(cron = "0 0/1 * * * ?")
    @SchedulerLock(name = "scheduledTask", lockAtMostFor = THIRTY_MIN, lockAtLeastFor = ONE_MIN)
    public void run() {
        logger.info("Run scheduled task.");
    }

Чтобы поддерживать код для прямого вызова распределенных блокировок, создайте DistributedLockExecutor на основе LockProvider Shedlock:

public class DistributedLockExecutor {
    private final LockProvider lockProvider;

    public DistributedLockExecutor(LockProvider lockProvider) {
        this.lockProvider = lockProvider;
    }

    public <T> T executeWithLock(Supplier<T> supplier, LockConfiguration configuration) {
        Optional<SimpleLock> lock = lockProvider.lock(configuration);
        if (!lock.isPresent()) {
            throw new LockAlreadyOccupiedException(configuration.getName());
        }

        try {
            return supplier.get();
        } finally {
            lock.get().unlock();
        }
    }

}

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

    public String doBusiness() {
        return distributedLockExecutor.executeWithLock(() -> "Hello World.",
                new LockConfiguration("key", Instant.now().plusSeconds(60)));
    }

Пример проекта в этой статье использует распределенные блокировки на основе JDBC. Фактически, любой механизм, обеспечивающий атомарные операции, может использоваться для распределенных блокировок. Shedlock также предоставляет механизмы реализации распределенных блокировок на основе Redis, ZooKeeper и Hazelcast.

Единый стиль кода

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

  • Класс данных запроса клиента единообразно использует один и тот же суффикс, например Command
  • Данные, возвращаемые клиенту, единообразно используют один и тот же суффикс, например Resetation.
  • Унифицированная структура процессов для обработки запросов, например, с использованием традиционной трехуровневой архитектуры или тактического режима DDD.
  • Обеспечивает последовательный возврат исключений (см. раздел «Обработка исключений»).
  • Обеспечить унифицированный класс структуры подкачки
  • Четкая классификация тестов и унифицированный базовый класс тестов (см. раздел «Автоматическая классификация тестов»).

Статическая проверка кода

Статическая проверка кода в основном включает следующие плагины Gradle. Конкретную конфигурацию см. в примере кода в этой статье:

  • Checkstyle: используется для проверки формата кода и стандартизации стиля кодирования.
  • Spotbugs: преемник Findbugs
  • Dependency check:OWASPПредусмотрены проверки безопасности библиотеки классов Java.
  • Sonar: отслеживание непрерывного улучшения кода.

медицинское обследование

Проверки работоспособности в основном используются в следующих сценариях:

  • Мы хотели бы изначально проверить, что программа работает правильно
  • Некоторое программное обеспечение для балансировки нагрузки будет определять доступность узла через URL-адрес проверки работоспособности.

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

Запустите пример проекта для этой статьи:

./run.sh

Затем получите доступ к API проверки работоспособности:http://localhost:8080/about, результат следующий:

{
  requestId: "698c8d29add54e24a3d435e2c749ea00",
  buildNumber: "unknown",
  buildTime: "unknown",
  deployTime: "2019-04-11T13:05:46.901+08:00[Asia/Shanghai]",
  gitRevision: "unknown",
  gitBranch: "unknown",
  environment: "[local]"
}

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

Документация API

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

Настройте Swagger следующим образом:

@Configuration
@EnableSwagger2
@Profile(value = {"local", "dev"})
public class SwaggerConfiguration {

    @Bean
    public Docket api() {
        return new Docket(SWAGGER_2)
                .select()
                .apis(basePackage("com.ecommerce.order"))
                .paths(any())
                .build();
    }
}

Начните локальный проект, посетитеhttp://localhost:8080/swagger-ui.html:

Swagger API文档

миграция базы данных

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

Пример проекта в этой статье использует Flyway в качестве инструмента миграции базы данных, добавляяFlywayПолагаясь наsrc/main/sources/db/migrationСоздайте файл сценария миграции в каталоге:

resources/
├── db
│   └── migration
│       ├── V1__init.sql
│       └── V2__create_product_table.sql

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

Мультисредовая сборка

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

  • локальный: для разработчиков для локальной разработки
  • ci: для непрерывной интеграции
  • dev: используется для совместной отладки интерфейсной разработки.
  • качество: для тестировщиков
  • uat: производственная среда для функционального принятия (иногда называемая промежуточной средой)
  • prod: официальная производственная среда

CORS

В системе с раздельными front-end и back-end, front-end разворачивается отдельно, а иногда даже доменное имя отличается от back-end, в этом случае требуется кросс-доменная обработка. Традиционный способ сделать это через JSONP, но это более «хитрый» способ, и более общая практика заключается в использованииCORSМеханизм в проекте Spring Boot включает конфигурацию CORS следующим образом:

@Configuration
public class CorsConfiguration {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }
}

Для проектов, использующих Spring Security, необходимо убедиться, что CORS работает до фильтров Spring Security, для этого Spring Security предоставляет соответствующие конфигурации:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			// by default uses a Bean by the name of corsConfigurationSource
			.cors().and()
			...
	}

	@Bean
	CorsConfigurationSource corsConfigurationSource() {
		CorsConfiguration configuration = new CorsConfiguration();
		configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
		configuration.setAllowedMethods(Arrays.asList("GET","POST"));
		UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
		source.registerCorsConfiguration("/**", configuration);
		return source;
	}
}

Общие сторонние библиотеки классов

Вот некоторые из наиболее распространенных сторонних библиотек, которые разработчики могут использовать в соответствии с потребностями своего проекта:

  • Guava: Общая библиотека от Google
  • Apache Commons: Общие библиотеки классов от Apache.
  • Mockito: макеты в основном используются для модульного тестирования
  • DBUnit: Управление тестовыми данными базы данных в тестах.
  • RestAssured: для тестирования Rest API
  • Jackson 2: Сериализация и десериализация данных Json.
  • jjwt: аутентификация токена JWT
  • Lombok: Автоматически генерировать общий код Java, такой как метод equals() и т. д.;
  • Feign: Клиент декларативного отдыха
  • Tika: для точного определения типа файла
  • itext: создать файл PDF и т. д.
  • zxing: Генерация QR-кода
  • Xstream: более легкая библиотека обработки XML, чем Jaxb.

Суммировать

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


Текст/ThoughtWorks Тэн ЮньДля получения более замечательных идей, пожалуйста, обратите внимание на публичный аккаунт WeChat: ThoughtWorks Insights