A better design pattern

I have a go service that collects some data i’m interested in and exposes it via grpc. The data collection portion I start as a “service” inside my main. But as I add functionality to the program, I can tell theres likely a better way to do things…for instance:

snippet from main:

func (app *Application) startServcies() {
	// pass the config to the rtsc service
	rtscWatcher := services.ErcotRtscWatcher{
		Details:  &app.Config.Details.RTSC,
		InfoLog:  app.InfoLog,
		ErrorLog: app.ErrorLog,
		Stream:   app.RtscStream,
	}
	sppWatcher := services.ErcotSppWatcher{
		Details:  &app.Config.Details.SPP,
		InfoLog:  app.InfoLog,
		ErrorLog: app.ErrorLog,
		Stream:   app.SppStream,
	}
	// start the watcher
	go rtscWatcher.Run()
	go sppWatcher.Run()

where I am implementing the Run method on each service type and then instantiating them one by one inside main.

an example Run method:

func (s *WeatherMonitor) Run() {
	// ensure user config doesn't do anything cheeky like set runtime to 0
	if err := ServiceValidator(s.Details); err != nil {
		s.ErrorLog.Println(err)
		return
	}
	// unpack the time and zone from the config
	t, z := s.Details.StartAt[0], s.Details.StartAt[1]
	// set the zone
	tz, _ := time.LoadLocation(z)

	if !s.Details.Scheduled {
		// this branch starts immediately
		s.InfoLog.Printf("%v is starting. running for %vs every %vs", s.Details.Name, s.Details.Runtime, s.Details.Refresh)
		for i := 0; i < (s.Details.Runtime / s.Details.Refresh); i++ {
			go GetWeather(s.Stream, s.ErrorLog)
			msg := <-s.Stream
			s.Store = append(s.Store, &msg)
			time.Sleep(time.Duration(s.Details.Refresh) * time.Second)
		}
		if !s.Details.ReRun {
			s.InfoLog.Println("terminating", s.Details.Name)
			return
		}
		s.InfoLog.Println(s.Details.Name, "rotating service")
	}
}

suggestions?

There’s nothing “(code) smelly” to me in the two snippets you provided. I’m imagining if I had to work on that code base and needed to fix a problem in, for example, the weather monitor, I might start in main, find your startServices, then where the weather monitor is configured and started and then dig into Run from there, etc. That is, it seems like a sensible (again, based on the 2 snippets; I don’t actually see where your WeatherMonitor itself is created or started, but if it’s like your ErcotRtscWatcher and ErcotSppWatcher, then it seems reasonable to me), readable design :person_shrugging:.

Is there some, I guess I’ll say, “quality” you’re looking to improve? Maybe not having startServices list every implementation? Maybe a way to enable/disable certain services? Something else?

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.