# Collaborating on architecture  Note: I'm Daniel, a backend engineer at Dropbox. Most of this is the reflection of my prior role as a staff engineer at Pento. To my surprise, making sure that teams collaborate and align on architecture was the biggest challenge. Taking everyone on the full journey of an architectural decision made the biggest difference. Meaning, everyone had to be involved in understanding context, the possible options, evaluating strengths and weakness, and selecting the way forward. --- ### 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. Note: Raise your hands, if you've done that! 1. You've been assigned as a reviewer to a big pull request. 2. You are discussing how to solve a problem, but someone is hell-bent on particular solution. 3. All architectural decisions are made by single person. --- ### Large pull requests I was working on a feature. There was this architectural issue. One thing led to the other, and...  Note: - This is not an invitation to a discussion. - Big PRs rarely get proper reviews. - Attachment to the written solution. - Defenders and aggresors - Managers won't be happy to throw it away. --- ### Being too excited for specific solution  Note: - "Top ten benefits to use Kafka" - What's the broader context? - Is it a problem? - Is it a problem we want to solve now? - Are we looking for a quick fix or a long-term fix? --- ### Dictating from the ivory tower  [Scaling the Practice of Architecture, Conversationally](https://martinfowler.com/articles/scaling-architecture-conversationally.html) Note: - Great way to lose motivated, driven, and creative people. - A single person doesn't have all the answers. --- ### Approaches that have helped me --- ### Decision records  [Read more on my blog](https://danielantos.com/articles/fostering-collaborative-architecture-decision-records/) Note: - Decision records are a centralized archive of decisions that have been taken or are pending. - Three functions: thinking tool, archive, and collaboration facilitator. - Invitation to a discussion. --- ### Diagrams  Note: - Fires different parts of the brain. - I was intimidated by diagrams. - Diagram can say more than pages of text. - You can edit diagrams together. --- ### Proof of Concept (PoC)  Note: - Invitation to discussion. - Easy to discard. --- ### Themes for these approaches - Show the journey. - What are the worst ways to solve the problem? - At which point the other opinion is better? --- ### How to continue the conversation in Go  -- ### main.go can tell a story about your service ```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() } ``` -- ### Restrict access ```go type API interface { SendNotification(templateID string, userID string) error } ``` ```go type APIImplementation struct { TemplateRepo TemplateRepo UserRepo UserRepo SmsSender SmsSender } notifications := api.APIImplementation{ TemplateRepo: infra.TemplateRepo{}, UserRepo: infra.UserRepo{}, SmsSender: infra.SmsSender{}, } ``` ```go 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, }, } } ``` 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   -- <!-- hidden slide --> ### Folder structure is a good guide  Domain Driven Design?  bids.go seems more important -- <!-- hidden slide --> ### Let types tell the story ```go 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 ```go // 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!** 