Birth of a Go Microservice

Once upon a Time

I have an application that has been serving me well in various areas, it'a well-crafted monolith, it's some enterprisey Java, but does well what it's meant for.

Every now and then I update the dependencies, maybe add some feature.

The Jar Hell

This time around Java mail broke. I said 'broke', but it wasn't exactly clear how, it was rather more silent than usual.

Scratching below the surface, I was using some Resteasy lib for mime parsing, which carried with itself a specific version of the ubiquitous java mail.

Not too bad if it was doable to update to the latest 'javax.mail'. As it turns out as a result of legal intricacies between Oracle and the Eclipse Foundation starting in 2019 (3 years ago...) the 'javax.mail' package has been largely renamed 'jakarta.mail'.

Have you ever heard the word 'jar hell' ? That's eventually one: multiple versions of the same artifact, none really knows if they can work together or if you're luck and the last one will work for all of the dependencies. Regardeless, the symptom is the same: a lot of time wasted into figuring out...

A Possible Fix

Since I'm not really passionate about those controversies, until I need to actually deal with them, and all I need is to actually send out a simple text message, I was thinking about a webservice that takes the message as:

{"subject":"A subject","body":"some damn text","recipient":"folks@some-place.com"}

The Code

And the fact is - I also had such an implementation ready, exactly with the same stack I was having trouble with. Possibly there I could have avoided the whole 'javax.mail' to 'jakarta.mail' madness... and be done with it.

Still it didn't feel right. It could be done in a simpler way, say all you have to do is read some configuration file. Open Vim and start typing:

package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
)

type SmtpConfig struct {
	Address  string `json:"address"`
	Sender   string `json:"sender"`
	Password string `json:"password"`
	Host     string `json:"host"`
	Port     string `json:"port"`
	CertFile string `json:"certfile"`
	KeyFile  string `json:"keyfile"`
}

func loadConfig(filename string) SmtpConfig {
	var conf SmtpConfig
	raw, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Println("Error occured while reading config")
	}
	json.Unmarshal(raw, &conf)
	return conf
}

Then I need to send a damn message, how hard can it be ?

package main

import (
	"fmt"
	"net/smtp"
	"os"
)

func SendMessage(from string, to []string, message []byte) {
	var c = loadConfig(os.Getenv("HOME") + "/.smtpservice.json")

	auth := smtp.PlainAuth("", from, c.Password, c.Host)

	err := smtp.SendMail(c.Host+":"+c.Port, auth, from, to, message)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("SENT")
}

And sure, make a Rest webservice out of this stuff, served over HTTPS and proper certificates, sure: why not?

package main

import (
	"encoding/base64"
	"net/http"
	"os"

	"github.com/gin-gonic/gin"
)

func main() {
	var c = loadConfig(os.Getenv("HOME") + "/weirdconf.json")
	address := c.Address

	router := gin.Default()
	router.SetTrustedProxies([]string{"127.0.0.1"})
	gin.SetMode(gin.ReleaseMode)

	router.POST("/message", dispatchMail)

	//router.Run(address)
	router.RunTLS(address, c.CertFile, c.KeyFile)
}

type EmailMessage struct {
	Subject   string `json:"subject" binding:"required"`
	Body      string `json:"body" binding:"required"`
	Recipient string `json:"recipient" binding:"required"`
}

func dispatchMail(c *gin.Context) {
	var email EmailMessage
	c.BindJSON(&email)

	subject := email.Subject
	decodedBody, err := base64.StdEncoding.DecodeString(email.Body)
	if err != nil {
		panic(err)
	}

	sender := "bot@my-evil-domain.org"
	recipient := []string{email.Recipient}
	message := []byte("Subject:" + subject + "\n" + "From:" + sender + "\n" + string(decodedBody))

	SendMessage(sender, recipient, message)
	c.IndentedJSON(http.StatusCreated, gin.H{"sender": sender, "subject": subject})
}

I can say I got this off the ground pretty quickly and no IDE was involved in the making.

It seems like a good use-case for Golang to me, and I'll be doing more of it, expecially since certificate handling goes pretty smooth and has native support from the language.



[java] [golang] [certificate] [git]