Go interfaces make development easier
I've read about and seen the power of Go interfaces in other people's code, but haven't really come across a good opportunity to use one until I wanted to stop wasting SMTP emails during development.
The app that runs this website has some function to send out emails with a Send(...) method on a Mailer struct. I used a single method interface sot hat I could choose to send out SMTP emails or log one to the console instead. This is similar to selecting smtp.EmailBackend
or console.EmailBackend
in Django.
Before implementing an interface
I have a mailer struct that holds the configuration in my application to send emails. The relevant bits look something like:
package mailer
import "github.com/go-mail/mail/v2"
// Mailer struct holds configuration to send emails
type Mailer struct {
dialer *mail.Dialer
sender string
}
And that mailer struct gets embedded into an application (server) struct so my web application can send emails.
package main
type application struct {
mailer mailer.Mailer
}
func main() {
app := &application{
mailer: mailer.New(...),
}
app.serve()
}
This setup is rather inflexible. The only option I have is to use and send SMTP emails. Next I'll walk through the steps to implement an interface that can send SMTP emails or log them to the console.
Step 1: Create an interface
In package mailer, create a new MailerInterface
.
package mailer
type MailerInterface interface {
Send(recipient, templateFile string, data any) error
}
And then the application struct definition is updated to reference the interface.
package main
type application struct {
mailer mailer.MailerInterface
}
Step 2: Create a LogMailer
struct
The LogMailer
will have a Send(...) method so that it fulfills the definition of a MailerInterface
package mailer
// LogMailer object for logging emails instead of sending them
type LogMailer struct {
log *slog.Logger
}
// NewLogMailer creates a new logMailer object for logging emails instead of sending them
func NewLogMailer(l *slog.Logger) LogMailer {
return LogMailer{
log: l,
}
}
// Send method logs the recipient email, template file name, and any dynamic data
// as an any parameter.
func (m LogMailer) Send(recipient, templateFile string, data any) error {
m.log.Info("send email", "to", recipient, "template", templateFile, "data", data)
return nil
}
Step 3: Choose when to use the Mailer
or LogMailer
I added a flag to choose when I want my application to use the Mailer
or LogMailer
.
package main
func main() {
var logMail bool
flag.BoolVar(&logMail, "logMail", false, "Log mail instead of sending a real email")
flag.Parse()
// Choose a mailer
var m mailer.MailerInterface
switch {
case logMail:
m = mailer.NewLogMailer(logger)
default:
m = mailer.New(...)
}
Tadaa! Now I can include or omit the flag -logMail
when launching my web app to choose whether emails should sent or logged.