Collaborating on architecture

What are we talking about today?

  • Why does collaboration matter?
  • When good intentions go south
  • Approaches that have helped me
  • How to continue conversation in Golang

Why does collaboration matter?

  • The best ideas come from clashing opinions
  • If everyone is rowing in the same direction,
    we will get there faster

When good intentions go south

I believe that we all have good intentions.

And even the flawed approach is better than inactivity.

Large pull requests

I was working on a feature. There was this architectural issue. One thing led to the other, and...

Being too excited for specific solution

Dictating from the ivory tower

Approaches that have helped me

Decision records

Diagrams

Proof of Concept (PoC)

  • Show the journey.
  • What are the worst ways to solve the problem?
  • At which point the other opinion is better?

Themes for these approaches

How to continue the conversation in Go

func main() {
	s := setup.New()

	notification, err := notification_setup.NewSetup()
	if err != nil {
		log.Fatalf("Could not set up no module: %v", err)
	}

	booking, err := booking_setup.New(s.Enqueueer, notification.API)
	if err != nil {
		log.Fatalf("Could not set up booking module: %v", err)
	}

	go booking.ListenForEvents(s.Dequeueer)
	go notification.PeriodicRetry()

	metrics.RegisterRoutes(s.Router)
	booking.RegisterRoutes(s.Router.Group("/booking"))
	s.StartServer()
}

main.go can tell a story about your service

type api struct {
	TemplateRepo TemplateRepo
	UserRepo     UserRepo
	SmsSender    SmsSender
}

func New(templateRepo TemplateRepo, userRepo UserRepo, sender SmsSender) API {
	return withLogs{
		API: api{
			TemplateRepo: templateRepo,
			UserRepo:     userRepo,
			SmsSender:    sender,
		},
	}
}

Restrict access

type APIImplementation struct {
	TemplateRepo TemplateRepo
	UserRepo     UserRepo
	SmsSender    SmsSender
}

notifications := api.APIImplementation{
    TemplateRepo: infra.TemplateRepo{},
    UserRepo:     infra.UserRepo{},
    SmsSender:    infra.SmsSender{},
}
type API interface {
	SendNotification(templateID string, userID string) error
}

We control creation,
user knows only about the interface

Restrict access

"internal" folder restricts who can use it

Use cyclic dependency to express (limit) what developer can do

Folder structure is a good guide

Domain Driven Design?

bids.go seems more important

Let types tell the story

func init() {
	var bids auction.Bids
	var tricks int

	contract := bids[len(bids)-1]
	CountPoints(contract, tricks)
}

func CountPoints(
	contract auction.Bid,
	tricks int,
) int {
	return 0
}

Nothing stops us from passing the wrong thing

// Domain
type Contract Bid

func (bs Bids) Contract() Contract

// Scoring
func init() {
	var bids auction.Bids
	var tricks int

	CountPoints(bids.Contract(), tricks)
}

func CountPoints(
	contract auction.Contract,
	tricks int,
) int {
	return 0
}

Little work to create a new type, but developer will be warned about a wrong type usage

Summary

  • Don't do it alone
  • Find a way to collaborate that fits you and your team
  • The best architecture doesn't matter, if you don't have buy-in
  • Expressing ideas in the code explicitly lengthen their lives
  • Don't do it alone

Thanks!

Let's have some questions!

☝️My blog
danielantos.com

☝️Slides

Made with Slides.com