Skip to content

Commit cceea87

Browse files
committed
More FAQ entries; closes #15
1 parent d0c3bdb commit cceea87

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

_src/faq/index.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ Experience has taught me that the most comprehensible, maintainable, and express
146146
of defining and wiring up the component graph in a microservice
147147
is through an explicit and declarative composition in a large func main.
148148

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.
149152
This style reinforces two important virtues.
150153
By strictly keeping component lifecycles in main,
151154
you avoid leaning on global state as a shortcut,
@@ -155,6 +158,28 @@ And if components are scoped to main,
155158
is to pass them explicitly as parameters to constructors.
156159
This keeps dependencies explicit, which stops a lot of technical debt before it starts.
157160

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.
158183
For more general Go design tips, see
159184
[Go best practices, six years in](https://peter.bourgon.org/go-best-practices-2016/).
160185

@@ -293,3 +318,44 @@ h = httptransport.NewServer(...)
293318
h = newRecoveringMiddleware(h, ...)
294319
// use h normally
295320
```
321+
322+
## Persistence — 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

Comments
 (0)