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]