Another version of xenvman is released! This time it’s 2.1.0. The only new feature is template importing.

Let’s take a quick look at an example to figure how this feature could be useful.

Let’s say every service in our microservices-based backend requires pretty much exactly the same setup - dependencies, location of a configuration file, invocation pattern etc. And thus it makes sense to create a single generic template with all those preparation steps collected in one place.

However if our service A depends on a large number of other services, our environment init code can become a bit messy:

	env := cl.MustCreateEnv(&def.InputEnv{
		Name:        "test-env",
		Templates: []*def.Tpl{
			{
				Tpl: "generic-service",
				Parameters: def.TplParams{"service": "B"},
			},
			{
				Tpl: "generic-service",
				Parameters: def.TplParams{"service": "C"},
			},
			{
				Tpl: "generic-service",
				Parameters: def.TplParams{"service": "D"},
			},
			{
				Tpl: "generic-service",
				Parameters: def.TplParams{"service": "E"},
			},
			{
				Tpl: "generic-service",
				Parameters: def.TplParams{"service": "F"},
			},
		}})

It would be nice to pack all these deps into a template and specify just one entry in the call to MustCreateEnv.

But prior to version 2.1.0 it wasn’t really possible. We’d have to create a brand new template and replicate all the preparation steps again. Which doesn’t look smooth, if you ask me.

Luckily with the new template importing feature in-place it’s a piece of cake:

meta-a.tpl.js:

function execute(tpl, params) {
   import_template("generic-service", {"service": "B"});
   import_template("generic-service", {"service": "C"});
   import_template("generic-service", {"service": "D"});
   import_template("generic-service", {"service": "E"});
   import_template("generic-service", {"service": "F"});
}

Then in our test code we simply call:

	env := cl.MustCreateEnv(&def.InputEnv{
		Name:        "test-env",
		Templates: []*def.Tpl{
			{ Tpl: "meta-a", },
		}})

and voilà! Our init block is as trivial as it can get and we avoided code duplication as well!

One final note is about how to access containers info in case of imported templates.

In the first example (where we didn’t use any imported templates), getting container info is usually done like this:

contF, err := env.GetContainer("generic-service", 4, "container")

that is - take a container named container from the template generic-service with index 4 (service F in our case).

On the other hand, when importing is involved, the setup is a little bit different. In order to avoid placing all the containers in the same flat list (which would pretty much prevent us from being able to figure out which index belongs to which template) imported templates are attached to the parent one in a tree-like manner.

So our meta-a template has five child templates underneath. And so in order to access these child templates a new function called GetContainerByPath(path []string, contName string) must be used.

Each item in the path parameter represents a step in this tree traversal and must be formatted using the following template: <template>|<index>.

In order to access container F info we’d call:

contF, err := env.GetContainerByPath(
	[]string{"meta-a|0", "generic-service|4"}, "container")

which corresponds to the tree path meta-a.0 -> generic-service.4 -> container.

As you can see, this new template importing feature should improve template composability and simplify code reuse.