@@ -146,6 +146,9 @@ Experience has taught me that the most comprehensible, maintainable, and express
146
146
of defining and wiring up the component graph in a microservice
147
147
is through an explicit and declarative composition in a large func main.
148
148
149
+ Inversion of control is a common feature of other frameworks,
150
+ implemented via Dependency Injection or Service Locator patterns.
151
+ But in Go kit, you should wire up your entire component graph in your func main.
149
152
This style reinforces two important virtues.
150
153
By strictly keeping component lifecycles in main,
151
154
you avoid leaning on global state as a shortcut,
@@ -155,6 +158,28 @@ And if components are scoped to main,
155
158
is to pass them explicitly as parameters to constructors.
156
159
This keeps dependencies explicit, which stops a lot of technical debt before it starts.
157
160
161
+ As an example, let's say we have the following components:
162
+
163
+ - Logger
164
+ - TodoService, implementing the Service interface
165
+ - LoggingMiddleware, implementing the Service interface, requiring Logger and concrete TodoService
166
+ - Endpoints, requiring a Service interface
167
+ - HTTP (transport), requiring Endpoints
168
+
169
+ Your func main should be wired up as follows:
170
+
171
+ ``` go
172
+ logger := log.NewLogger (...)
173
+
174
+ var service todo.Service // interface
175
+ service = todo.NewService () // concrete struct
176
+ service = todo.NewLoggingMiddleware (logger)(service)
177
+
178
+ endpoints := todo.NewEndpoints (service)
179
+ transport := todo.NewHTTPTransport (endpoints)
180
+ ```
181
+
182
+ At the cost of having a potentially large func main, composition is explicit and declarative.
158
183
For more general Go design tips, see
159
184
[ Go best practices, six years in] ( https://peter.bourgon.org/go-best-practices-2016/ ) .
160
185
@@ -293,3 +318,44 @@ h = httptransport.NewServer(...)
293
318
h = newRecoveringMiddleware (h, ...)
294
319
// use h normally
295
320
```
321
+
322
+ ## Persistence &mdash ; How should I work with databases and data stores?
323
+
324
+ Accessing databases is typically part of the core business logic.
325
+ Therefore, it probably makes sense to include an e.g. * sql.DB pointer in the concrete implementation of your service.
326
+
327
+ ``` go
328
+ type MyService struct {
329
+ db *sql.DB
330
+ value string
331
+ logger log.Logger
332
+ }
333
+
334
+ func NewService (db *sql .DB , value string , logger log .Logger ) *MyService {
335
+ return &MyService{
336
+ db: db,
337
+ value: value,
338
+ logger: logger,
339
+ }
340
+ }
341
+ ```
342
+
343
+ Even better: consider defining an interface to model your persistence operations.
344
+ The interface will deal in business domain objects, and have an implementation that wraps the database handle.
345
+ For example, consider a simple persistence layer for user profiles.
346
+
347
+ ``` go
348
+ type Store interface {
349
+ Insert (Profile) error
350
+ Select (id string ) (Profile, error )
351
+ Delete (id string ) error
352
+ }
353
+
354
+ type databaseStore struct { db *sql.DB }
355
+
356
+ func (s *databaseStore ) Insert (p Profile ) error { /* ... */ }
357
+ func (s *databaseStore ) Select (id string ) (Profile , error ) { /* ... */ }
358
+ func (s *databaseStore ) Delete (id string ) error { /* ... */ }
359
+ ```
360
+
361
+ In this case, you'd include a Store, rather than a * sql.DB, in your concrete implementation.
0 commit comments