Job — шаблон проектирования для новичков и опытных Go программистов

Я начал программировать на Go после достаточно продолжительного периода программирования на PHP. Полагаю судя по последним тенденциям, мой случай далеко не единичный. Go в целом набирает популярность среди Web разработчиков.

Итак, я в мире сусликов. А что делает прожженный до мозга костей PHP программист оказавшись там? Правильно он продолжает “пыхтеть” – в силу своей профессиональной деформации — но уже на Go, со всеми вытекающими отсюда последствиями.

Признаюсь я никогда не был на собеседовании, устраиваясь на вакансию Go разработчика, но подозреваю, что там не задают вопросов про SOLID, Dependency Injection и различные прелести ООП. Там другой мир, другая парадигма (в целом разумеется не экстраординарная), связанная с параллельным программированием, строгой типизацией и отсутствием полноценного ООП в строгом его смысле – к тому ООП к которому мы привыкли по PHP, С++ или Java.

Поэтому в начале, разумеется, был определенный дискомфорт и ломка шаблонов. Я не понимал как использовать тот же content.Context, зачем он? Мои шаблоны готовы уже были треснуть, но матерого PHP программиста без хрена не съешь. Пора что-то с этим делать и писать свой велосипед! А именно то решение, которое мне, как PHP программисту, казалось достаточно очевидным для решения задача связанных с параллельным программирование, краеугольным камнем Go. Усевшись за рабочий ноутбук я почти месяц корпел над работой. Я хотел не просто сделать реализацию сферического коня в вакууме, а показать на конкретном примере возможность использования данного подхода. Так сказать, proof of concept. Ну и заодно набить руку на Go, чего греха таить.

После того как велосипед был готов, я выкатил его, осмотрел и, выдохнув, сказал сам себе: “ну, блин, вроде неплохо получилось. Нужно срочно рассказать об этом сообществу, пускай оценят“. Сказано — сделано. На Reddit была опубликована небольшая заметка про новый шаблон проектирования Go и, судя по реакции я понял… что люди ничего не поняли. “Зачем? Как это использовать? Так это же over-engineering” – в целом так можно охарактеризовать реакцию. Я не сдавался: “сейчас я вам нафотошоплю свой контраргумент. Ага, добавить это в README.md – пусть знают эти суслики“.

Как видят решение программисты

А если без шуток, то это, наверное, тема для отдельной статьи.

В целом о чем я? Много воды – ноль конкретики. Чтобы было реализовано:

  1. Шаблон проектирования Job – это поведенческий шаблон проектирования, который инкапсулирует всю необходимую информацию для параллельного выполнения задач (task).

  2. В качестве примера использования данного шаблона был реализован простой прокси-сервер, выполняющий роль балансировщика уровня L4; клиент, который сканируют указанную директорию на наличие изображений и отправляет все найденные изображения на backend сервер для изменения их размера; backend сервер, который обрабатывает запросы по изменению размера изображений. В основе всех трех приложений лежит компонент Job. Код так же доступен в репозитории на Github.

В целом сам шаблон Job – это аналог хорошо известного Command pattern, но с прицелом на параллельное выполнение. В той же статье есть и конкретный пример его использования и для нашего случая:

Parallel Processing Where the commands are written as tasks to a shared resource and executed by many threads in parallel (possibly on remote machines; this variant is often referred to as the Master/Worker pattern)

Выполнение задач зависит друг от друга, если одна задача прерывает свое выполнение из-за ошибки — все остальные задачи останавливаются тоже. Это все это, безусловно, можно реализовать с помощью content.Context, но реализация на более высоком уровне позволяет избавиться от рутинных действий, связанных с организацией параллельного выполнения задач и сконцентрироваться на реализации самой бизнес логики. Ну а сами ошибки очень легко отлавливать вызовами специальных методов вроде task.Assert заменяя ими более емкие конструкции if err != nil { panic(err) }.

Задачи разделают данные и оркестрируют свое выполнение с помощью так называемой ping/pong синхронизации. Я приведу здесь лишь небольшой кускок кода, чтобы дать общее представление — полностью библиотека доступна в репозитории на Github по ссылке.

// Saves resized image to the output dir
func (s *ImageResizer) SaveResizedImageTask(j job.Job) (job.Init, job.Run, job.Finalize) {
	// Do some initialization here
	init := func(t job.Task) {
		if _, err := os.Stat(s.inputDir); os.IsNotExist(err) {
			t.Assert(err)
		}
		if _, err := os.Stat(s.outputDir); os.IsNotExist(err) {
			err := os.Mkdir(s.outputDir, 755)
			t.Assert(err)
		}
	}
	run := func(task job.Task) {
		stream := j.GetValue().(netmanager.Stream)
		select {
		case finishedTask := <- j.TaskDoneNotify(): // Wait for the scanner task to be done
			if finishedTask.GetIndex() == s.scanneridx {
				s.scandone = true
			}
			task.Tick()
		case frame := <-stream.RecvDataFrame(): // Process response from the backend server
			task.AssertNotNil(frame)
			res := &imgresize.Response{}
			err := frame.Decode(res)
			task.Assert(err)

			baseName := fmt.Sprintf("%s-%dx%d%s",
				res.OriginalName, res.ResizedWidth, res.ResizedHeight, res.Typ.ToFileExt())
			filename := s.outputDir + string(os.PathSeparator) + baseName
			if ! s.dryRun {
				ioutil.WriteFile(filename, res.ImgData, 0775)
			}

			j.Log(1) <- fmt.Sprintf("file %s has been saved", filename)
			stream.RecvDataFrameSync() // Tell netmanager.ReadTask that we are done processing the frame
			s.recvx++
			task.Tick()
		default:
			switch {
			case s.scandone && s.recvx == s.sentx: // Check if all found images were processed
				task.FinishJob()
			default:
				task.Idle() // Do nothing
			}
		}
	}
	return init, run, nil
}

Это одна из задач клиента, которая обрабатывает приходящий ответ от сервера и сохраняет полученное изображение. Задача оркестирует свое выполнение — используя вышеупомянутую технику ping/pong синхронизации – с задачей, которая занимается файловым сканированием. Также она определяет, когда пришел последний ответ от сервера и когда нужно завершить выполнение всей работы (Job).

Насколько это решение over-engineered и насколько его использование вместо content.Context оправдано – пусть это решает читатель, я своё мнение выразил в виде сарказма на изображении выше.

Всем хороших выходных и да прибудет с нами сила пэхэпе в мире сусликов.

Let’s block ads! (Why?)

Read More

Recent Posts

Деньги дороже свободы: Telegram ограничивает доступ к некоторым каналам по требованию Apple

Павел Дуров сообщил, что Telegram начнет цензурировать контент по требованию Apple. В противном случае приложение Telegram может быть удалено из…

3 часа ago

TEAMLY представил обновленную версию: платформа позволит управлять проектами и автоматизировать работу отделов

Реклама. ООО КьюСофт, ИНН 7714594610 Erid: 2SDnjdVN7Cd Теперь в ТИМЛИ можно выстроить полный цикл работы отдела: генерировать идеи и бизнес-цели в интеллект-картах…

8 часов ago

VK Cloud запустила облачный сервис Cloud Kafka

Облачная платформа VK Cloud запустила сервис Cloud Kafka. Это решение на базе технологий с открытым исходным кодом Apache Kafka и…

24 часа ago

Администрация президента не поддержала поправки Минцифры о смягчении штрафов за утечки данных

Администрация президента отклонила предложенные Минцифры поправки ко второму чтению законопроекта об оборотных штрафах за утечку персональных данных. Они предусматривали смягчение…

2 дня ago

Google подтвердил, что ссылки для ранжирования уже не так важны

Google сейчас придает меньшее значение ссылкам при ранжировании сайтов, чем раньше. Об этом сообщил сотрудник поисковика Гэри Илш, выступая на…

2 дня ago

Студенты Яндекса выиграли международную студенческую олимпиаду по программированию

Студенты факультета компьютерных наук, созданного Яндексом совместно с Высшей школой экономики, выиграли международную студенческую олимпиаду по программированию ICPC. Это одно…

3 дня ago