Job - a design pattern for beginners and experienced Go programmers

I started programming in Go after a fairly long period of programming in PHP. I suppose, judging by the latest trends, my case is far from being an isolated one. Go in general is gaining popularity among Web developers.





So, I'm in the gopher world. And what does a PHP programmer burned to the core do when he is there? That's right, he continues to "puff" - due to his professional deformation - but already on Go, with all the ensuing consequences.





, Go , , SOLID, Dependency Injection . , ( ), , - PHP, ++ Java.





, , . context.Context, ? , PHP . - ! , , PHP , , Go. . , . , proof of concept. Go, .





, , , , : ", , . , ". — . Reddit Go , … . "? ? over-engineering" - . : " . , README.md - ".





How programmers see the solution

, , , .





? - . :





  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)





, - — . , , context.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
}
      
      







This is one of the tasks of the client, which processes the incoming response from the server and stores the resulting image. The task orchestrates its execution - using the aforementioned ping / pong synchronization technique - with a task that does file scanning. It also determines when the last response from the server came and when to complete the execution of all the work (Job).





To what extent this solution is over-engineered and to what extent its use instead of context.Context is justified - let the reader decide, I expressed my opinion in the form of sarcasm in the image above.





Have a great weekend everyone and may the power of pehape be with us in the world of gophers.








All Articles