Fxforce5: code rewriter for DI with Uber FX

I am working on a project (closed source, sorry) that has suffered from proliferation of parameters to constructors, and for this (and other) reasons looks like it would benefit from a DI approach, such as Uber FX.

The code base is pretty large at this point, and manually adapting it is slow, tedious and frustrating. But laziness, impatience and hubris says: why not use features like static analysis using DST and reflection to automate this process?

So here is an attempt at doing that: https://github.com/debedb/fxforce5.

P.S. For a related approach used to automatically generate Swagger docs, see Swagger as you Go.

Some more random notes

  • Completely agree with @mipsytipsy here:

    I am an extremely literal person, and literally speaking, nobody can be a “full stack” engineer. The idea is a ridiculous one. There’s too much stack! But that’s not what people mean when they say it. They mean, “I’m not just a frontend or backend engineer. I span boundaries.”
  • Yeah, this blog is for bragging.
  • What is it with fillable PDFs on some gov’t websites (I know, I know; that’s a post for a different day) — but they can be sometimes saved but not printable?
  • TFW about 16 years later after your colleague writes an impassioned call to “Tear down that GIL!” (take that, Mr. Gorbachev!), the GIL is finally torn down.
  • I was wondering what the Go team was smoking when they came up with the reference date concept and can I have some of that?
  • What is it that causes Medium to suck so much? Is it all the useless “content creators” writing things pre-GPT that just rephrase stuff from the Internet with nary a value added (“Here’s 10 reasons to learn Python”, and here’s how to write “Hello, world” in C, did you know?)? Is it that now probably thousands more are using generative AI — kinda indistinguishable? Or is it their idiotic subscription model which cannot deal with some logins? (I should really devote time to figure out that one but why — is that platform really worth anything at all?)

Adventures with Golang dependency injection

Just some notes as I am learning this… There aren’t good answers here, mostly questions (am I doing it right?). All these examples are a part of (not very well) organized GitHub repo here.

Structure injection

Having once hated the magic of Spring’s DI, I’ve grown cautiously accustomed to the whole @Autowired stuff. When it comes to Go, I’ve come across Uber’s Fx framework which looks great, but I haven’t been able to figure out just how to automagically inject fields whose values are being Provided into other structs.

An attempt to ask our overlords yielded something not very clear.

I finally broke down and asked a stupid question. Then I found the answer — do not use constructors, just use fx.In in combination with fx.Populate(). Finally this works. But doesn’t seem ideal in all cases…

Avoiding boilerplate duplication

This is all well and good, but not always. For example, consider this example in addition to the above:

package dependencies

import "go.uber.org/fx"

type Foo string

type Bar string

type Baz string

type DependenciesType struct {
	fx.In

	Foo Foo
	Bar Bar
	Baz Baz
}

func NewFoo() Foo {
	return "foo"
}

func NewBar() Bar {
	return "bar"
}

func NewBaz() Baz {
	return "baz"
}

var Dependencies DependenciesType

var DependenciesModule = fx.Options(
	fx.Provide(NewFoo),
	fx.Provide(NewBar),
	fx.Provide(NewBaz),
)

If I try to use it as dependencies.Dependencies, it’s ok (as above). But if I rather want to get rid of this var, and rather use constructors. But I don’t like the proliferation of parameters into constructors. I can use Parameter objects but I’d like to avoid the boilerplate of copying fields from the Parameter object into the struct being returned, so I’d like to use reflection like so (generics are nice):

package utils

import "reflect"

func Construct[P any, T any, PT interface{ *T }](params interface{}) PT {
	p := PT(new(T))
	construct0(params, p)
	return p
}

func construct0(params interface{}, retval interface{}) {
	// Check if retval is a pointer
	rv := reflect.ValueOf(retval)
	if rv.Kind() != reflect.Ptr {
		panic("retval is not a pointer")
	}

	// Dereference the pointer to get the underlying value
	rv = rv.Elem()

	// Check if the dereferenced value is a struct
	if rv.Kind() != reflect.Struct {
		panic("retval is not a pointer to a struct")
	}

	// Now, get the value of params
	rp := reflect.ValueOf(params)
	if rp.Kind() != reflect.Struct {
		panic("params is not a struct")
	}

	// Iterate over the fields of params and copy to retval
	for i := 0; i < rp.NumField(); i++ {
		name := rp.Type().Field(i).Name
		field, ok := rv.Type().FieldByName(name)
		if ok && field.Type == rp.Field(i).Type() {
			rv.FieldByName(name).Set(rp.Field(i))
		}
	}

}

So then I can use it as follows:

package dependencies

import (
	"example.com/fxtest/utils"
	"go.uber.org/fx"
)

type Foo *string

type Bar *string

type Baz *string

type DependenciesType struct {
	Foo Foo
	Bar Bar
	Baz Baz
}

type DependenciesParams struct {
	fx.In
	Foo Foo
	Bar Bar
	Baz Baz
}

func NewFoo() Foo {
	s := "foo"
	return &s
}

func NewBar() Bar {
	s := "bar"
	return &s
}

func NewBaz() Baz {
	s := "foo"
	return &s
}

func NewDependencies(params DependenciesParams) *DependenciesType {
	retval := utils.Construct[DependenciesParams, DependenciesType](params)
	return retval
}

var DependenciesModule = fx.Module("dependencies",
	fx.Provide(NewFoo),
	fx.Provide(NewBar),
	fx.Provide(NewBaz),

	fx.Provide(NewDependencies),
)

But while this takes care of proliferating parameters in the constructor as well as the boilerplate step of copying, I still cannot avoid duplicating the fields between DependenciesType and DependenciesParams, running into various problems.

Looks like this is still TBD on the library side; I’ll see if I can get further.

Conditional Provide

When using constructors, I would have a construct such as:

type X struct {
   field *FieldType
}

func NewX() *X {
   x := &X{}
   if os.Getenv("FOO") == "BAR" {
     x.field = NewFieldType(...)
   }
}

In other words, I wanted field to only be initialized if some environment variable is set. In transitioning from using constructors to fx.Provide(), I wanted to keep the same functionality, so I came up with this:

type XType struct {
   fx.In

   field *FieldType `optional:"true"`
}

var X XType

func NewX() *X {
   x := &X{}
   if os.Getenv("FOO") == "BAR" {
     x.field = NewFieldType(...)
   }
}

var XModule = fx.Module("x",
	func() fx.Option {
		if os.Getenv("FOO") == "BAR" {
			return fx.Options(
				fx.Provide(NewFieldType),
			)
		}
		return fx.Options()
	}(),
	fx.Populate(&X),

Works fine. But is it the right way?


Putting on my marketing hat: Random MailJet hack

(Yes, I do indeed wear multiple hats — marketing FTW, or is it WTF?)

Really wanted to use MailJet (BTW, guys, what’s with support and not being able to edit a campaign after launch? Get what I pay for?) to send users a list of items, dynamically. For example, say I have the following list of items, say, “forgotten” in a shopping cart:

User,Items
Alice,”bat, ball”
Bob,”racquet, shuttlecock”


And I want to send something like (notice I’d also like links there):

Hey Alice, did you forget this in your cart:

Ball
Bat

Turns out, loop construct doesn’t work. Aside: this is despite ChatGPT’s valiant attempt to suggest that something like this could work:

{{
{% set items = data.items|split(‘,’) %}
{% for item in items %}
{{ item.strip() }}
{% endfor %}
}}


But the answer is hacky but works. Because if you remember that SGML is OK with single quotes, then construct your contacts list like so:

User,Items

Alice,"<a href='https://www.amazon.com/BB-W-Wooden-baseball-bat-size/dp/B0039NKEZQ/'>bat</a>,<a href='https://www.amazon.com/Rawlings-Official-Recreational-Baseballs-OLB3BBOX3/dp/B00AWVNPMM/'>ball</a>
Bob,"<a href='https://www.amazon.com/YONEX-Graphite-Badminton-Racquet-Tension/dp/B08X2SXQHR/'>racquet</a>, <a href='https://www.amazon.com/White-Badminton-Birdies-Bedminton-Shuttlecocks/dp/B0B9FPRHBF'>shuttlecock</a>"




And make the HTML be just

Hey [[data:user:””]],

You did you forget this in your cart?
[[data:items:””]]


Works!

P.S. Links provided here are just whatever I found on Google, no affiliate marketing.

Fun with ChatGPT

So prompt engineering is all the rage (a friend of mine even wrote a bookyou magnificent bastard, I haven’t read it yet) — and I have a problem with trying to export GCP configuration for use with Deployment manager. Specifically, how to get CloudRun to work.

So an impressive session (and another one) result with some cool reasoning by ChatGPT in response to me pointing out that things don’t quite work (yes, Amigo Grady, I know it’s not reasoning)… Except that they don’t work.

Which I finally find via StackOverflow — because this feature is unsupported, per Google. But this info is probably outside the training data window of our esteemed LLM.

What’s the moral of the story? There is no moral — use it as a tool. Just a tool.

More random notes

  • Yeah, no-code/low-code is great (wave, OpenAI). Especially for growth-hacking, right (hello, Butcher)? But here’s your no-code platform — Google Ads. Gawd… I’d rather write code.
  • Why does FastAPILoggingHandler seems to ignore my formatter? I don’t know; but the fact that someone else also spends time figuring out the inane things that should just work is quite frustrating.
  • How many yaks have you shaved today?
  • O, GCP, how convenient: in the env var YAML sent to gcloud run you helpfully interpret things like “on”/”off”, “true”/”false”, “yes”/”no” as numbers, eh? And then you crash with:

    ERROR: gcloud crashed (ValidationError): Expected type <class 'str'> for field value, found True (type <class 'bool'>)

    Because of course you do.
  • “Overriding a number of default settings is key to shaving off unnecessary spend”. Yep.

Validation?

I recently learned about MediaMath’s custom brain. Excited for the validation of OpenDSP‘s concept of custom bidding logic. But it is a bit limited, being just a polynomial. MediaMath’s custom bid router provides way more flexibility — but you need your own infrastructure! So — I still think our approach — DSL-based scripting — is better, because it combines both!

Random notes for January 2023

Not enough for any singular entry, but enough to write a bunch of annoyed points. Because I hate Twitter threads and this is the reverse: unconnected entries jammed together.

  • GoLang: Looks like the answers to my questions are nicely written up here.
  • Technology and Society: Ok, I promise I’ll get to geekery here. So, PeopleCDC folks seem upset about the New Yorker article. But now, I am surprised — and maybe it is an oversight — at the lack of inclusion of IT people in the form. Artists, yeah, to carry the message — but if the goal is to slow the spread, why no consideration given to automation of various things (look at how pathetic most government websites are for things that are routine).

    Not expecting to hear back, really.
  • Google Ads and API Management: Every time you think you get used to all the various entities in Google Ads, you realize there’s of course a sunsetting of UA … Of course! Of course this is where I pause and let Steve Yegge on with his rant:

    Dear RECIPIENT,

    Fuck yooooouuuuuuuu. Fuck you, fuck you, Fuck You. Drop whatever you are doing because it’s not important. What is important is OUR time. It’s costing us time and money to support our shit, and we’re tired of it, so we’re not going to support it anymore. So drop your fucking plans and go start digging through our shitty documentation, begging for scraps on forums, and oh by the way, our new shit is COMPLETELY different from the old shit, because well, we fucked that design up pretty bad, heh, but hey, that’s YOUR problem, not our problem.

    We remain committed as always to ensuring everything you write will be unusable within 1 year.

  • API Management, ListHub: First, I learned there’s a standards body Unsure what I’m making of it (I mean, I suppose I’ve gotten good results from IAB, and standardization of FinOps is somewhat ongoing, so, er, maybe not all bureaucracy is an awful horrible crap.

    But that’s kind of a side note.