Here is a list of patterns!
Domain-Driven Design is an approach to software development that centers the development on programming a domain model that has a rich understanding of the processes and rules of a domain
Idea: Have a layer between a business domain and a datastore
Create an interface to define datastore handling logic. This way the domain service logic will not depend on a specific datastore implementation, but on the interface.
Below are the Steps.
service/task.go
.// The interface to be implemented by the actual datastore
type TaskRepository interface {
Create()
Find()
Update()
}
// Domain struct
type Task struct {
repo TaskRepository
}
func NewTask(repo TaskRepository) *Task {
return &Task{repo: repo}
}
// Illustrations of the wrapping methods on the domain struct
func (t *Task) Create(ctx context.Context, id string) {
t.repo.Create(ctx, id)
}
func (t *Task) Find(ctx context.Context, id string) {
t.repo.Find(ctx, id)
}
func (t *Task) Update(ctx context.Context, id string) {
t.repo.Update(ctx, id)
}
mongoDB/mongo.go
.type MongoDBRepository struct {
url string
}
func NewMongoDBRepository(url string) *MongoDBRepository {
return &MongoDBRepository{url: url}
}
func (m *MongoDBRepository) Update(ctx context.Context, id string) {
// Update the datastore via some query
}
func (m *MongoDBRepository) Find(ctx context.Context, id string) {
// Find the datastore via some query
}
func (m *MongoDBRepository) Create(ctx context.Context, id string) {
// Create the datastore via some query
}
repo
can be swapped out by a mock implementation for testing purposes.repo := mongoDB.NewMongoDBRepository("db url")
taskService := service.NewTask(repo)
task, err := taskService.Create(context.Background(), "1")
if err != nil {
panic(err)
}
Idea: Call multiple services of a similar signature in a concise way
This should be quite clear by looking at the following example.
func main() {
// Here comes the duplication
v1, err1 := Service1(context.Background())
if err1 != nil {
panic(err1)
}
v2, err2 := Service2(context.Background())
if err2 != nil {
panic(err1)
}
v3, err3 := Service3(context.Background())
if err3 != nil {
panic(err1)
}
}
func Service1(ctx context.Context) (int64, error) {
time.Sleep(100 * time.Millisecond)
return 1, nil
}
func Service2(ctx context.Context) (int64, error) {
time.Sleep(200 * time.Millisecond)
return 2, nil
}
func Service3(ctx context.Context) (int64, error) {
time.Sleep(300 * time.Millisecond)
return 3, nil
}
// Abstract the signature of the services
type Service func(ctx context.Context) (int64, error)
func main() {
services := []Service{Service1, Service2, Service3}
for _, service := range services {
v, err := service(context.Background())
if err != nil {
panic(err)
}
fmt.Println(v)
}
}
func Service1(ctx context.Context) (int64, error) {
time.Sleep(100 * time.Millisecond)
return 1, nil
}
func Service2(ctx context.Context) (int64, error) {
time.Sleep(200 * time.Millisecond)
return 2, nil
}
func Service3(ctx context.Context) (int64, error) {
time.Sleep(300 * time.Millisecond)
return 3, nil
}
Idea: an isolation layer created to consume values from other system, acting as a translation layer in both directions
package anticorruption
// import the two packages
func Kelvin(counutry string) float64 {
// Call the Kelvin package here
return kelvin.Calculate(country) - 273.15
}
func Fahrenheit(country string) float64 {
// Call the Fahrenheit package here
return (5.0/9.0) * (fahrenheit.New(country).Value() -32)
}
Call the package in the service layer with a consistent signature.
func main() {
kelvin := anticorruption.Kelvin("US")
fahrenheit := anticorruption.Fahrenheit("US")
}