В этой статье я хотел бы показать вам, что многие считают, что CI — это правда постоянная интеграция, а что нет CI. Кроме того, я дам вам несколько примеров, чтобы лучше понять это.
Что такое CI?
CI (Акроним для непрерывной интеграции) представляет собой практику разработки программного обеспечения, в которой непрерывный интеграционный сервер опрос репозиторий управления версиями создает артефакт и проверяет артефакт с набором определенных тестов. Это обычная практика для большинства предприятий и частных лиц … И это не настоящее определение непрерывного интеграции, извините за шутку.
Что такое настоящая постоянная интеграция?
Ну, истинная непрерывная интеграция не «просто» какая-то «Дженкинс | Трэвис | Go | Teamcity», которые опросают репозиторий Git проекта, компилирует его и запустить кучу испытаний против артефакта. На самом деле, это менее интересная часть CI, которая не является технологией (Дженкинс), но провилегированная практика, созданная Гарди Боч и принята и предписана Экстремальная методология программирования Отказ
Как аналогия с другой техникой экстремальных программиров TDD не о модульные тестирования (Хотя он использует тестирование подразделения), но о обратной связи, о получении обратной связи как можно скорее ускорить циклы разработки (что реализуется в конкретном использовании модуля тестирования).
С CI программное обеспечение построено несколько раз в день (в идеале каждые несколько часов), каждый раз, когда разработчик объединяет код в основной линии (который должен быть часто), чтобы избежать «интеграции ада» (слияние кода из разных событий в конце Взаимодействие развития). CI позволяет избежать этой «интеграции ад» путем интеграции кода как можно скорее и принуждать членов команды для просмотра того, что делают другие разработчики для принятия общих командных решений о новом коде.
Методология гласит, что каждый член команды интегрируется в основные линии, насколько это возможно. Каждый вклад в VCS (система контроля версий) потенциально является выпуском, поэтому каждый вклад не должен нарушать функциональность и должен пройти все известные тесты.
Сервер CI построит артефакт из последних источников магистрали и пройдет все известные тесты. Если есть неудача, то Ci предупреждает всех членов команды штата Создать (красный).
Максимальный приоритет команды состоит в том, чтобы сохранить сборку по умолчанию (зеленый).
Что не CI?
Как только мы поняли, что CI гораздо больше, чем простое использование CI Server, мы можем утверждать, что:
- Работа с функциональными ветвями и иметь CI Checking Master не CI
- Работа с запросами по тяги не CI
Важно отметить, что я не сужу с точки зрения хороших/плохих практик, как функциональные ветви и запросы на тягу, они просто другие методологии, отличные от CI.
Обе функциональные ветви и запросы на тягу полагаются на разных ветви, чем мастер (тот, который контролируется сервером Ci), это приводит к более длинным циклам, прежде чем они могут быть объединены в Master.
Особенности филиалов и потянутых запросов, которые глубоко полагаются на групповую планирование ресурсов/задач, чтобы избежать рефакторов на одной задаче (филиал), которая затрагивает события на другой задаче (филиал), которым требуется, минияющая резьба «интеграция ада».
Пример интеграции ада: У нас есть следующий код, два класса, которые используют обратные вызовы API для внешнего API:
APIUsersAccessor class APIUsersAccessor { const USERS_API_PATH = "/users"; /** * @var string */ private $host; /** * @var string */ private $username; /** * @var string */ private $password; public function __construct(string $host, string $username, string $password) { $this->host = $host; $this->username = $username; $this->password = $password; } public function getAllUsers(): array { $data = array( "email" => $this->username, "password" => $this->password ); $headers = array( "Content-Type" => "application/json;charset=UTF-8" ); $request = \Requests::GET($this->host.self::USERS_API_PATH, $headers, json_encode($data)); return json_decode($request->body); } } APIProductsAccessor class APIProductsAccessor { const PRODUCTS_API_PATH = "/products"; /** * @var string */ private $host; /** * @var string */ private $username; /** * @var string */ private $password; public function __construct(string $host, string $username, string $password) { $this->host = $host; $this->username = $username; $this->password = $password; } public function getAllProducts(): array { $data = array( "email" => $this->username, "password" => $this->password ); $headers = array( "Content-Type" => "application/json;charset=UTF-8" ); $request = \Requests::GET($this->host.self::PRODUCTS_API_PATH, $headers, json_encode($data)); return json_decode($request->body); } }
Как вы можете видеть, оба кода очень похожи (является дублирование классического кода). Теперь мы собираемся начать две функции развития с 2 филиалами разработки. Первое разработка должна добавить номер телефона к запросу на API продуктов, второй должен создать новую API для запроса всех автомобилей, доступных в магазине. Это код в API продуктов после добавления номера телефона:
APIUsersAccessor (with telephone) class APIUsersAccessor { .... public function __construct(string $host, string $username, string $password) { ....... $this->telephone = $telephone; } public function getAllUsers(): array { $data = array( "email" => $this->username, "password" => $this->password, "tel" => $this->telephone ); ..... } }
Хорошо, разработчик добавил недостающее поле и добавил его к запросу. Разработчик филиала 1 ожидает, что этот различий в качестве слива с мастером:
Но проблема в том, что Developer1 не знает, что разработчик 2 сделал рефактором, чтобы уменьшить дублирование кода, потому что Carapi слишком похож на USERAPI и ProductaPi, поэтому код в его ветке будет таким:
BaseAPIAccessor abstract class BaseAPIAccessor { private $apiPath; /** * @var string */ private $host; /** * @var string */ private $username; /** * @var string */ private $password; protected function __construct(string $host,string $apiPath, string $username, string $password) { $this->host = $host; $this->username = $username; $this->password = $password; $this->apiPath = $apiPath; } protected function doGetRequest(): array { $data = array( "email" => $this->username, "password" => $this->password ); $headers = array( "Content-Type" => "application/json;charset=UTF-8" ); $request = \Requests::GET($this->host.$this->apiPath, $headers, json_encode($data)); return json_decode($request->body); } } concrete APIs class ApiCarsAccessor extends BaseAPIAccessor { public function __construct(string $host, string $username, string $password) { parent::__construct($host, "/cars", $username, $password); } public function getAllUsers(): array { return $this->doGetRequest(); } } class APIUserAccessor extends BaseAPIAccessor { public function __construct(string $host, string $username, string $password) { parent::__construct($host, "/users", $username, $password); } public function getAllUsers(): array { return $this->doGetRequest(); } } class APIProductsAccessor extends BaseAPIAccessor { public function __construct(string $host, string $username, string $password) { parent::__construct($host, "/products", $username, $password); } public function getAllProducts(): array { return $this->doGetRequest(); } }
Так что реальное слияние будет:
Таким образом, в основном у нас будет большой конфликт в конце цикла развития, когда будет объединиться в отрасль1 и отрасль2 в магистрату. Нам придется сделать много обзоров кода, которые будут включать археологический процесс рассмотрения всех решений прошлого в этапе развития и посмотреть, как объединить код. В этом конкретном случае номер телефона также будет включать в себя какое-то переписывание.
Некоторые утверждают, что Developer2 не должен был сделать рефактору, потому что планирование заявило, что он должен развивать только Carapi, а планирование четко заявляло, что не должно быть столкновения с USERAPI. Ну да… Но сделать то, что такого рода экстремальные планификационные работы должны быть хорошее планирование всех ресурсов, у нас должно быть много архитектурных встреч с участием Developer1 и Developer2.
В этих архитектурных встречах Developer1 и Developer2 должны были реализовать, что существует какой-то дублирование кода, и они должны решать o intermene и repens, или ничего не делайте и увеличивают техническую задолженность, перемещая решение рефакторов на будущие итерации. Это может не звучать на Agile, верно? Но точка в том, что трудно смешать Agile и неимущие практики.
Если мы сделаем функции филиала/вытягивания запросов на полный итеративный процесс планификации, работают лучше, если у нас есть Agile непрерывная интеграция, является правильным инструментом. Опять же, я не указываю, что функция ветвей/запросов на тягах являются хорошими/плохими инструментами, я просто заявляю, что они невывильные практики.
Agile — это все о связи, все о непрерывном улучшении, и это все о обратной связи как можно скорее. В Agile Acke Developer1 будет знать о рефакторинге Developer2 в начале, возможность начать диалог с разработчиком1 и проверить, является ли тип абстракции, который он предлагает, будет правильным, чтобы соответствовать также добавлению номера телефона.
Да, но ждать! Мне нужна функция ветви! Что если не все функции доставлены в конце итерации?
Функциональные ветви — это решение проблемы: что делать, если не все код доставляется в конце итерации, но это не единственное решение.
CI имеет другое решение этой проблемы — «Обновление функций». Функциональные филиалы изолируют функцию прогресса работы от конечного продукта через ветку (W.I.P. Живет в отдельной копии кода), функция переключается изолировать функцию от остальной части кода, используя .. Code!
Самая простая функция Toggle можно написать — это страшный, если он-else-else, является примером, который вы найдете в большинстве сайтов, когда вы погружаетесь «Функция Toggle». Это не единственный способ реализации, так как любой другой тип программного обеспечения, которую вы можете заменить эту условную логику с полиморфизмом.
В этом примере в Slim мы создаем нынешнюю итерацию новую конечную точку отдыха, мы не хотели быть готовыми к производству, у нас есть этот код:
code prior the toggling [ 'displayErrorDetails' => true, 'logger' => [ 'name' => "dexeus", 'level' => Monolog\Logger::DEBUG, 'path' => 'php://stderr', ], ], ]; $app = new \Slim\App( $config ); $c = $app->getContainer(); $c['logger'] = function ($c) { $settings = $c->get('settings'); $logger = LoggerFactory::getInstance($settings['logger']['name'], $settings['logger']['level']); $logger->pushHandler(new Monolog\Handler\StreamHandler($settings['logger']['path'], $settings['logger']['level'])); return $logger; }; $app->group("", function () use ($app){ OriginalEndpoint::get()->add($app); //we are registering the endpoint in slim });
Мы можем определить переключательную функцию простым предложением
if clause feature toggle group("", function () use ($app){ OriginalEndpoint::get()->add($app); if(getenv("APP_ENV") === "development") { NewEndpoint::get()->add($app); // we are registering the new endpoint if the environment is set to development (devs machines should have APP_ENV envar setted to development) } });
И мы можем уточнить наш код, чтобы лучше выразить то, что мы делаем, и сможем иметь несколько сред (возможно, для того, чтобы иметь ситуацию с тестом AB?)
configuration map feature toggle add($app); }; $aEnvironment = function ($app){ productionEnvironment($app); NewEndpointA::get()->add($app); }; $bEnvironment = function ($app){ productionEnvironment($app); NewEndpointB::get()->add($app); }; $develEnvironment = function ($app){ productionEnvironment($app); NewEndpointInEarlyDevelopment::get()->add($app); }; $configurationMap = [ "production" => $productionEnvironment, "testA" => $aEnvironment, "testB" => $bEnvironment, "development" => $develEnvironment ]; $app->group("", function () use ($app, $configurationMap){ $configurationMap[getenv("APP_ENV")]($app); });
Преимущества этого метода согласованы с главной целью CI (имеющие постоянную обратную связь о интеграции кода/проверки/и столкновения с другими событиями), код в процессе разработан и развернут в производство, и у нас постоянные отзывы о интеграции Новая функция с остальной частью кода, используя риск включения функции, когда она разработана.
Это хорошая практика, чтобы удалить этот вид переключателей из кода, как только новая функция стабилизирована, чтобы избежать добавления сложности к кодовой базе.
Хорошо, мы приехали в конце этой первой части истинной непрерывной интеграции. Мы заново открыли, что непрерывная интеграция — «не только», используя CI Server, но принятие практики с настойчивостью и дисциплиной. Во второй части мы поговорим о том, как моделировать хороший поток Ci.
Чтобы не пропустить вторую часть настоящей непрерывной интеграции, подпишитесь на нашу ежемесячную рассылку здесь Отказ
Если вы нашли эту статью о истинной постоянной интеграции, вам может понравиться …
Scala Generics I: Границы типа Scala
Scala Regrics II: ковариация и контравариация
BDD: тестирование пользовательского интерфейса
F-связанный над универсальным типом в Scala
Минервисов против монолитной архитектуры
» Почти бесконечное «масштабируемость
Преимущества истинной непрерывной интеграции
Рабочее программное обеспечение над количеством функций
Преимущества TDD
Преимущества Дженкинс
Истинная постоянная интеграция с Fastlane & Jenkins в iOS
Пост Вернуться к корням: к истинной непрерывной интеграции (часть одной части) появился первым на Apiumhub Отказ
Оригинал: «https://dev.to/apium_hub/back-to-the-roots-towards-true-continuous-integration-part-one-14m7»