Last active
June 12, 2018 23:13
-
-
Save dgellow/956d94916a638f0024f3d3b12cfb3fb8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Should be in a 'domain', or 'entities'. Here we define our domain | |
// model. | |
package main | |
// A User is a citizen, that can be or not innocent. | |
type User struct { | |
ID string | |
Email string | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"os" | |
) | |
// main parses potential flags and env vars, instanciate dependencies | |
// and inject them into the service via its constructor. | |
func main() { | |
// Setup a connection to a real database | |
db := NewRealDB("realdb://evilcorp.org:1234") | |
// Instantiate a client for our surveillance API | |
apiClient := NewRealAPIClient("api.evilcorp.org/metrics") | |
// Inject dependencies in our service | |
service := NewService(db, apiClient) | |
// Start our service | |
fmt.Println("Starting service...") | |
if err := service.Run(); err != nil { | |
fmt.Fprintln(os.Stderr, err) | |
os.Exit(1) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Should be in a package 'app', with the rest of the business logic. | |
package main | |
// UserRepository represents an abstraction of a data store that | |
// returns and potentially manipulate users. | |
type UserRepository interface { | |
GetAll() ([]User, error) | |
} | |
// Overlord is a system doing surveillance on citizens | |
type Overlord struct { | |
// Access to stored user data (could be a postgres db, an | |
// in-memory, etc) | |
users UserRepository | |
// Access to a 'surveillance API', could be EvilCorp, or other | |
// 3rd parties providing similar features. | |
client APIClient | |
} | |
// NewOverlord is a constructor | |
func NewOverlord(users UserRepository, client APIClient) *Overlord { | |
return &Overlord{ | |
users: users, | |
client: client, | |
} | |
} | |
// ApplySurveillance is the actual process managing surveillance on | |
// users. | |
func (ov *Overlord) ApplySurveillance() error { | |
users, err := ov.users.GetAll() | |
if err != nil { | |
return err | |
} | |
for _, u := range users { | |
if err := ov.client.SendSurveillanceMetrics(u); err != nil { | |
return err | |
} | |
} | |
return nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Should be in a package 'adapters/evilcorp', with other things | |
// specific to EvilCorp. | |
package main | |
import "log" | |
// RealAPIClient is an almost realistic client for the evilcorp API | |
type RealAPIClient struct { | |
apiURL string | |
} | |
// NewRealAPIClient is a constructor | |
func NewRealAPIClient(apiURL string) *RealAPIClient { | |
return &RealAPIClient{ | |
apiURL: apiURL, | |
} | |
} | |
// Post something to the evilcorp API | |
func (client *RealAPIClient) post(s ...string) error { | |
log.Printf("realapi client: POST request: %q\n", s) | |
// do something real | |
return nil | |
} | |
func (client *RealAPIClient) SendSurveillanceMetrics(user User) error { | |
return client.post(user.ID, user.Email) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Should be in a package 'infra/dbclient' | |
package main | |
import ( | |
"log" | |
) | |
// RealDB is client to an almost real database | |
type RealDB struct { | |
// In reality conn would be a db connection | |
conn interface{} | |
} | |
// NewRealDB is a constructor | |
func NewRealDB(connstring string) *RealDB { | |
return &RealDB{ | |
// should create a db connection here, created with | |
// connstring, or get it directly as a parameter as | |
// that could potentially return an error, and | |
// returning errors from constructors is gross. | |
conn: nil, | |
} | |
} | |
// Query sends the query to the actual database and return the | |
// response. | |
func (db *RealDB) Query(statement string) error { | |
log.Printf("realdb query: %q\n", statement) | |
// do something real | |
return nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Should be in a package 'cmd/sevice' | |
package main | |
import "time" | |
// DB is an abstraction of a database connection | |
type DB interface { | |
Query(string) error | |
} | |
// APIClient is an abstraction of a client to a surveillance API | |
type APIClient interface { | |
SendSurveillanceMetrics(User) error | |
} | |
// Service is our actual service. The main use is to abstract | |
// dependencies as a way to be able to swap them or replace them by | |
// fake, stubs or mock versions. | |
type Service struct { | |
db DB | |
client APIClient | |
overlord *Overlord | |
} | |
// NewService is a constructor, used for dependencies injection | |
func NewService( | |
db DB, | |
client APIClient, | |
) *Service { | |
userRepo := NewRealUserRepository(db) | |
return &Service{ | |
db: db, | |
client: client, | |
overlord: NewOverlord(userRepo, client), | |
} | |
} | |
// Run starts the service | |
func (s *Service) Run() error { | |
c := time.Tick(1 * time.Second) | |
for _ = range c { | |
if err := s.process(); err != nil { | |
return err | |
} | |
} | |
return nil | |
} | |
func (s *Service) process() error { | |
return s.overlord.ApplySurveillance() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"testing" | |
) | |
func TestService(t *testing.T) { | |
db := stubDB{} | |
apiClient := stubAPIClient{} | |
service := NewService(db, apiClient) | |
for i := 0; i < 1000; i++ { | |
err := service.process() | |
if err != nil { | |
t.Fatal("expected no error, got one:", err) | |
} | |
} | |
} | |
type stubDB struct { | |
capturedStmt string | |
err error | |
} | |
func (s stubDB) Query(statement string) error { | |
s.capturedStmt = statement | |
return s.err | |
} | |
type stubAPIClient struct { | |
capturedUser User | |
err error | |
} | |
func (s stubAPIClient) SendSurveillanceMetrics(user User) error { | |
s.capturedUser = user | |
return s.err | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Should be in a package 'infra/db' | |
package main | |
type RealUserRepository struct { | |
db DB | |
} | |
func NewRealUserRepository(db DB) *RealUserRepository { | |
return &RealUserRepository{ | |
db: db, | |
} | |
} | |
func (repo *RealUserRepository) GetAll() ([]User, error) { | |
// should in reality do something like: | |
// records := repo.db.Query("select id, email from users where surveillance = true") | |
// then convert 'records' to a slice of User | |
repo.db.Query("select id, email from users where surveillance = true") | |
return []User{ | |
{"user1", "sam.e@example.org"}, | |
{"user2", "andre.v@example.org"}, | |
}, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment