Каждый день мы выполняем повторяющуюся работу в некоторых областях нашей жизни, некоторые вещи можно автоматизировать, это всегда было так, как нам повезло: это всегда область улучшения. Если вы обнаружите что-то, что вы делаете каждый день, и вы нашли способ автоматизировать это, вы можете попробовать и сделать некоторую автоматизацию, конечно, это может окупиться, только если автоматизированная работа занимает меньше времени, чем выполнение самой повторяющейся задачи. Модным примером этого могут быть автоматизированные автомобили, которые пытаются автоматизировать что-то конкретное: ежедневные поездки на работу; что повторяющаяся работа находится на пути к автоматизации, есть и другие вещи, которые также автоматизированы, например, процессы на производстве, которые раньше выполнялись рабочими, выполняющими повторяющуюся ручную работу. В разработке программного обеспечения в большинстве случаев стоит потратить время на автоматизацию, чтобы улучшить наш рабочий процесс, например: автоматизировать тестирование, развертывание, проверку нашего кода на правильность написания, правильный интервал и т. д., большинство распространенных применений уже автоматизированы. а для этих конкретных нам нужно действовать умно и автоматизировать их, максимально сократить повторяющуюся работу, ну, это было в случае с моей нынешней работой…

Проблема

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

  1. Создайте файлы в проектах React.
  2. Скопируйте эти файлы в исходный код монолита
  3. Обновите ссылки на эти файлы в файле ресурсов (поскольку наша версия проекта изменилась, и наши файлы пакетов Webpack также изменились)
  4. Мы готовы обновить браузер и увидеть наши изменения React поверх монолита.

Эти задачи были выполнены несколько раз, прежде чем мы смогли сказать, что он готов к слиянию и созданию PR (это еще один шаг, который мы неоднократно делали)

5. Создайте запрос на слияние с изменениями React поверх монолита с обновленной версией пакета.

6. Обновите тег Git

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

Часть автоматизации

Как производилась автоматизация?

Я начал работать над скриптом оболочки, который в основном просто копировал файлы из папки сборки в ресурсы в монолите, это было недостаточно амбициозно, мой друг предложил мне использовать Python (сначала я не был в этом уверен, но Решил попробовать, в итоге понравилось), поэтому автоматизацию продолжил на Python.

Одна проблема: скрипт автоматизации был написан на Python, но скрипты, которые мы запускаем в React, написаны на JS (Node), поэтому сначала мне нужно было выяснить, как запустить Python из Node, и это возможно с помощью пакета python-shell. Решено!

JavaScript

Осталось только определить скрипт запуска в нашем файле package.json:

"deploy-to-monolith": "node deployToMonolith.js"

Вот и все, теперь нам нужно ввести в действие некоторый код JS для запуска наших файлов сценариев .py:

развернутьToMonolith.js

const { PythonShell } = require('python-shell')
const REACT_PROJECT_FOLDER_NAME = 'myReactApp'
const scriptPath = './monolith-integration'
const {
npm_config_bumpPackageVersion,
npm_config_createPR,
npm_config_jiraTicketId,
npm_config_branchShortDesc = 'deployToMonolith',
} = process.env
// Follow instructions in the readme.md to know when to use parameters and when not to
// NOTE: PR should only be created when the version is bumped
const deployToMonolith = () => {
if (npm_config_bumpPackageVersion) {
let version = ''
const bumpPackageVersion = new PythonShell('bumpPackageVersion.py', {
scriptPath,
})
bumpPackageVersion.on('message', message => {
if (message) {
version = message
}
console.log(message)
})
bumpPackageVersion.end(error => {
console.log('Bump package version finished')
if (error) throw error
const pushToReact = new PythonShell('pushTagToReact.py', {
scriptPath,
args: [npm_config_jiraTicketId, version],
})
pushToReact.end(error => {
if (error) throw error
console.log(`git tag ${version} pushed`)
copyDistToMonolith(createPR, version)
})
})
} else {
// No need to bump package.json version but files need to be updated
copyDistToMonolith()
}
}
// Run this while TESTING integrations in Monolith run: `npm run deployToMonolith`
const copyDistToMonolith = (callback = () => {}, args) => {
console.log('Building project...')
PythonShell.run('buildProject.py', { scriptPath }, () => {
console.log('Build project finished')
console.log('Updating bundle files...')
PythonShell.run(
'copyDistToMonolith.py',
{ scriptPath, args: [REACT_PROJECT_FOLDER_NAME] },
error => {
console.log('Bundle and resources files in monolith updated')
if (error) throw error
callback(args)
}
)
})
}
const createPR = (version = '') => {
if (npm_config_createPR && npm_config_jiraTicketId) {
const createPR = new PythonShell('createPR.py', {
scriptPath,
args: [
npm_config_jiraTicketId,
npm_config_branchShortDesc,
version,
REACT_PROJECT_FOLDER_NAME,
],
})
console.log('Creating PR...')
createPR.on('message', message => {
console.log(message)
})
}
}
deployToMonolith()

Питон

bumpPackageVersion.py

import sys, fileinput, os
import re
package_version = "  \"version\""
package_version_template = package_version + ": \"{0}\",\n"
def update_package_version () :
for line in fileinput.input("package.json", inplace=True):
if line.startswith(package_version) :
new_version = get_new_version(line)
line = package_version_template.format(new_version)
sys.stdout.write(line)
print("v" + new_version)
def get_new_version (line) :
current_version = re.search('\d+\.\d+\.\d+', line)[0]
split_version = current_version.split(".")
# Currently this updates only the patch 0.0.X
split_version[2] = str(int(split_version[2], 10) + 1)
return ".".join(split_version)
update_package_version()

pushTagToReact.py

import os, sys, re, subprocess
jira_ticket_id = sys.argv[1]
version = sys.argv[2]
os.system("git add package.json")
os.system("git commit -m \"{0} - Bump version to {1} \"".format(jira_ticket_id, version))
os.system("git tag -a {0} -m \"Bump to {0}\"".format(version))
os.system("git push origin " + version)
os.system("git push origin " + re.search('.*heads\/(.+)\\\\', str(subprocess.run(["git symbolic-ref HEAD"], shell = True, capture_output = True))).group(1))

buildProject.py

import os
os.system("npm run build")

копироватьDistToMonolith.py

import os
import shutil
import fileinput, argparse, sys
project_name = sys.argv[1]
path_to_react_project = "/js/appReact/" + project_name + "/"
#this should be updated with whatever your project path is
resource_line_template = "resource url:'" + path_to_react_project + "{0}'\n"
# Copy the files from dist to appReact folder in monolith project; update the path with yours
path_to_monolith = "../monolith" #from the React App
full_path_to_react_project = path_to_monolith + "/" + path_to_react_project
def remove_old_files () :
for the_file in os.listdir(full_path_to_react_project):
file_path = os.path.join(full_path_to_react_project, the_file)
try:
if os.path.isfile(file_path):
os.unlink(file_path)
except Exception as e:
print(e)
def copy_files_to_monolith () :
files_to_replace = []
for root, dirs, files in os.walk("./dist"):
for file in files:
path_file = os.path.join(root,file)
if len(file.split(".")) == 3 :
files_to_replace.append(file)
shutil.copy2(path_file, full_path_to_react_project)
# we don't need the index.html
os.remove(full_path_to_react_project + "index.html")
print(files_to_replace)
return files_to_replace
def update_resources_file (files_to_replace) :
count = 0
for line in fileinput.input(path_to_monolith + "/monolith-app/conf/resourcesFile.java", inplace=True):
if path_to_react_project in line:
line = resource_line_template.format(files_to_replace[count])
count += 1
sys.stdout.write(line)
remove_old_files()
files_to_replace = copy_files_to_monolith()
update_resources_file(files_to_replace)

createPR.py

import os, sys, subprocess, re
import webbrowser
jira_ticket_id = sys.argv[1]
branch_short_desc = sys.argv[2] or ""
version = sys.argv[3] or "error in version"
project_name = sys.argv[4] or "error in project name"
branch_name = "feature_{0}_{1}".format(jira_ticket_id, branch_short_desc)
os.chdir("../monolith")
os.system("git checkout develop && git pull origin develop")
os.system("git checkout -b {}".format(branch_name))
os.system("git add monolith-app/conf/resourcesFile.java")
os.system("git add pathToReactChanges/appReact/{}/".format(sys.argv[4]))
os.system("git commit -m \"{0} - Deploy {1} {2} to monolith\"".format(jira_ticket_id, project_name, version))
batcmd = "git push origin " + branch_name
pr_url = re.search('(https:\/\/\S+)', str(subprocess.run([batcmd], shell = True, capture_output = True)))[0]
print(pr_url)
webbrowser.open(pr_url)

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

Вывод

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

Не стесняйтесь спрашивать меня, если у вас есть какие-либо вопросы, и помните, что любая повторяющаяся работа в большинстве случаев стоит того, чтобы ее автоматизировать.

Вы можете найти меня в Твиттере @adancarrasco

Спасибо за чтение!