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
Collaborating on architecture and expressing it in Go
By antosdaniel
Collaborating on architecture and expressing it in Go
- 71