This document outlines principles for writing maintainable code using a service-oriented architecture approach. It recommends defining reusable service objects that each focus on a single domain concept or business logic. These service objects should make their dependencies explicit through their constructor signatures. This makes the code more modular and testable. It also lays the foundation for potentially extracting services into microservices later. The document provides these recommendations and emphasizes that keeping code separated by concern and with well-defined dependencies leads to more maintainable code over time. It concludes by promising code examples demonstrating these principles.
2. Obligatory "Who am I" slide
● Sean Kelly (Hi!)
○ But everyone calls me Stabby
● I work for Tapjoy
○ We serve hundreds of millions of ads a day on a global scale
● I have a new puppy and a new kitty
○ Some of these slides might accidentally veer into tips on Crate Training
● I maintain a bunch of open source libraries
○ That literally nobody uses
○ It's actually kind of fun, since I can break things with less repurcussions
3. What am I here to talk about?
● Service Oriented Architecture
● Defining Dependencies
● Separation of Concerns
● Practical example of applying the above in a Go webservice
5. Service Oriented Architecture
● Many ways to define and apply this
● Internally architecting your application for SOA is a great pattern for maintaining
application code
● Keep crucial business logic out of Models and Controllers
○ Helpers like combining First + Last into FullName can live on a Model
○ Try to avoid ivory-towering yourself into code no one wants to maintain
● Centralize it in reusable Service objects
● These objects define domain boundaries in your systems
● These objects also define dependency maps between the parts of your systems
● Eventually, if you do break your app up into microservices, internal SOA has
already got your code in a "ready to extract" state
6. Dependencies
Ugh, is he gonna tell us to use some interface{} abusing DI
framework?
(Spoiler Alert: Nah)
7. Dependencies
● External resources you rely upon to execute code
● Common examples:
○ MySQL
○ Memcached
○ Riak
● Less Common examples:
○ Configuration data
○ Other code modules
● "Hmm, I need access to X here…"
○ That is probably a dependency
● Passing dependencies down via a constructor makes for more testable, reliable
code.
● Avoid "new-ing up" a dependency unless it's in your main() and handed to others
9. Separation of Concerns
● Modules of code should have 1 purpose
● Don't lump things together out of convenience
● Each distinct piece of your domain should get it's own Service object
● You can compose Service objects to create "Higher Order" Services
○ If Service A needs Service B, but Service B needs Service A…
○ You need Service C, composed of A and B
● So, if you had some basic Services like these:
○ UserService
○ ErrorService
○ ThirdPartyMailerService
● You might compose some Services like these:
○ AccountManagementService(UserService, ThirdPartyMailerService)
○ AlertingService(AccountService, ErrorService, ThirdPartyMailerService)
11. Testing
● Service Objects with their dependencies declared in their New method: 80% of
the way there
● The other 20%: Interfaces!
● If instead of this…
○ NewAlertingService(*AccountService, *ErrorService, *SomeEmailProvider) *AlertingService
● You did this…
○ NewAlertingService(IAccountService, IErrorService, IEmailService) IAlerteringService
● You could replace any of the critical dependencies in a unit test with mocks or no-
op implementations
● You can now use tools like `gomock` to generate test implementations for you
○ Or, just do it yourself if your services are small / have specialized testing needs
● Conversely, you could mock out the dependencies of those Services
○ But usually, whole-mocking is easier and more straightforward
13. Services, Dependencies, and You
● Primary focus is to keep code "clean" and DRY
● Each service does one thing, and does it well
● They define dependencies in the constructor / New method
● Create "Higher Order" Services by combining existing Services
● More easily testable with both unit and integration tests
○ Especially if you use Interfaces
● Lays the groundwork for extraction into microservices, should you choose to
○ If the code is cleanly separated, and has well defined dependencies, it's more straightforward to port
● And Now… Sample code!