-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🔥 add viper, cobra and continue post examples
- Loading branch information
1 parent
415851a
commit bd20fca
Showing
18 changed files
with
1,005 additions
and
7 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
MONGO_URI=mongodb://localhost:27017 | ||
MONGO_URI_TEST=mongodb://localhost:27017 | ||
MONGO_DB=databasename |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
.idea | ||
*.out | ||
*.out | ||
*.env |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package aggregate | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/google/uuid" | ||
"github.com/lfcifuentes/ddd-go/entity" | ||
) | ||
|
||
var ( | ||
// ErrMissingValues is returned when a product is created without a name or description | ||
ErrMissingValues = errors.New("missing values") | ||
) | ||
|
||
// Product is a aggregate that combines item with a price and quantity | ||
type Product struct { | ||
// item is the root entity which is an item | ||
item *entity.Item | ||
price float64 | ||
// Quantity is the number of products in stock | ||
quantity int | ||
} | ||
|
||
// NewProduct will create a new product | ||
// will return error if name of description is empty | ||
func NewProduct(name, description string, price float64) (Product, error) { | ||
if name == "" || description == "" { | ||
return Product{}, ErrMissingValues | ||
} | ||
|
||
return Product{ | ||
item: &entity.Item{ | ||
ID: uuid.New(), | ||
Name: name, | ||
Description: description, | ||
}, | ||
price: price, | ||
quantity: 0, | ||
}, nil | ||
} | ||
|
||
func (p Product) GetID() uuid.UUID { | ||
return p.item.ID | ||
} | ||
|
||
func (p Product) GetItem() *entity.Item { | ||
return p.item | ||
} | ||
|
||
func (p Product) GetPrice() float64 { | ||
return p.price | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package aggregate | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestProduct_NewProduct(t *testing.T) { | ||
type testCase struct { | ||
test string | ||
name string | ||
description string | ||
price float64 | ||
expectedErr error | ||
} | ||
|
||
testCases := []testCase{ | ||
{ | ||
test: "should return error if name is empty", | ||
name: "", | ||
expectedErr: ErrMissingValues, | ||
}, | ||
{ | ||
test: "validvalues", | ||
name: "test", | ||
description: "test", | ||
price: 1.0, | ||
expectedErr: nil, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.test, func(t *testing.T) { | ||
_, err := NewProduct(tc.name, tc.description, tc.price) | ||
if err != tc.expectedErr { | ||
t.Errorf("Expected error: %v, got: %v", tc.expectedErr, 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package cmd | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
// rootCmd represents the base command when called without any subcommands | ||
var rootCmd = &cobra.Command{ | ||
Use: "ddd-go", | ||
Short: "Implementation of DDD in Go", | ||
Long: ``, | ||
} | ||
|
||
// Execute adds all child commands to the root command and sets flags appropriately. | ||
// This is called by main.main(). It only needs to happen once to the rootCmd. | ||
func Execute() { | ||
err := rootCmd.Execute() | ||
if err != nil { | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/google/uuid" | ||
"github.com/lfcifuentes/ddd-go/aggregate" | ||
"github.com/lfcifuentes/ddd-go/services/order" | ||
"github.com/lfcifuentes/ddd-go/services/tavern" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
func init() { | ||
rootCmd.AddCommand(serveCmd) | ||
} | ||
|
||
// serveCmd represents the serve command | ||
var serveCmd = &cobra.Command{ | ||
Use: "serve", | ||
Short: "Starts the server", | ||
Long: ``, | ||
Run: startServer, | ||
} | ||
|
||
func startServer(_ *cobra.Command, _ []string) { | ||
// create products array | ||
products := CreateProducts() | ||
|
||
fmt.Println("Products:") | ||
for _, p := range products { | ||
fmt.Printf("Name: %s, Description: %s, Price: %f\n", p.GetItem().Name, p.GetItem().Description, p.GetPrice()) | ||
} | ||
|
||
// Create Order Service to use in tavern | ||
os, err := order.NewOrderService( | ||
order.WithMongoCustomerRepository(viper.GetString("MONGO_URI")), | ||
order.WithMemoryProductRepository(products), | ||
) | ||
if err != nil { | ||
fmt.Println("Error creating order service") | ||
panic(err) | ||
} | ||
|
||
// Create tavern service | ||
tavern, err := tavern.NewTavern(tavern.WithOrderService(os)) | ||
if err != nil { | ||
fmt.Println("Error creating tavern service") | ||
panic(err) | ||
} | ||
|
||
cus, err := os.AddCustomer("Percy") | ||
if err != nil { | ||
fmt.Println("Error creating customer") | ||
panic(err) | ||
} | ||
|
||
order := []uuid.UUID{ | ||
products[0].GetID(), | ||
} | ||
|
||
// Execute Order | ||
err = tavern.Order(cus, order) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
func CreateProducts() []aggregate.Product { | ||
apple, err := aggregate.NewProduct("Apple", "Red apple", 0.5) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
banana, err := aggregate.NewProduct("Banana", "Yellow banana", 0.3) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
orage, err := aggregate.NewProduct("Orange", "Orange orange", 0.7) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
return []aggregate.Product{ | ||
apple, | ||
banana, | ||
orage, | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Mongo is a mongo implementation of the Customer Repository | ||
package mongo | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/google/uuid" | ||
"github.com/lfcifuentes/ddd-go/aggregate" | ||
"github.com/spf13/viper" | ||
"go.mongodb.org/mongo-driver/bson" | ||
"go.mongodb.org/mongo-driver/mongo" | ||
"go.mongodb.org/mongo-driver/mongo/options" | ||
) | ||
|
||
type MongoRepository struct { | ||
db *mongo.Database | ||
// customer is used to store customers | ||
customer *mongo.Collection | ||
} | ||
|
||
// mongoCustomer is an internal type that is used to store a CustomerAggregate | ||
// we make an internal struct for this to avoid coupling this mongo implementation to the customeraggregate. | ||
// Mongo uses bson so we add tags for that | ||
type mongoCustomer struct { | ||
ID uuid.UUID `bson:"id"` | ||
Name string `bson:"name"` | ||
} | ||
|
||
// NewFromCustomer takes in a aggregate and converts into internal structure | ||
func NewFromCustomer(c aggregate.Customer) mongoCustomer { | ||
return mongoCustomer{ | ||
ID: c.GetID(), | ||
Name: c.GetName(), | ||
} | ||
} | ||
|
||
// ToAggregate converts into a aggregate.Customer | ||
// this could validate all values present etc | ||
func (m mongoCustomer) ToAggregate() aggregate.Customer { | ||
c := aggregate.Customer{} | ||
|
||
c.SetID(m.ID) | ||
c.SetName(m.Name) | ||
|
||
return c | ||
|
||
} | ||
|
||
// Create a new mongodb repository | ||
func New(ctx context.Context, connectionString string) (*MongoRepository, error) { | ||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(connectionString)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Find Metabot DB | ||
db := client.Database(viper.GetString("MONGO_DB")) | ||
customers := db.Collection("customers") | ||
|
||
return &MongoRepository{ | ||
db: db, | ||
customer: customers, | ||
}, nil | ||
} | ||
|
||
func (mr *MongoRepository) Get(id uuid.UUID) (aggregate.Customer, error) { | ||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
defer cancel() | ||
|
||
result := mr.customer.FindOne(ctx, bson.M{"id": id}) | ||
|
||
var c mongoCustomer | ||
err := result.Decode(&c) | ||
if err != nil { | ||
return aggregate.Customer{}, err | ||
} | ||
// Convert to aggregate | ||
return c.ToAggregate(), nil | ||
} | ||
|
||
func (mr *MongoRepository) Add(c aggregate.Customer) error { | ||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
defer cancel() | ||
|
||
internal := NewFromCustomer(c) | ||
_, err := mr.customer.InsertOne(ctx, internal) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (mr *MongoRepository) Update(c aggregate.Customer) error { | ||
panic("to implement") | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package memory | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/google/uuid" | ||
"github.com/lfcifuentes/ddd-go/aggregate" | ||
"github.com/lfcifuentes/ddd-go/domain/product" | ||
) | ||
|
||
type MemoryProductRepository struct { | ||
products map[uuid.UUID]aggregate.Product | ||
sync.Mutex | ||
} | ||
|
||
// New is a factory function to generate a new repository of products | ||
func New() *MemoryProductRepository { | ||
return &MemoryProductRepository{ | ||
products: make(map[uuid.UUID]aggregate.Product), | ||
} | ||
} | ||
|
||
func (mr *MemoryProductRepository) GetAll() ([]aggregate.Product, error) { | ||
products := make([]aggregate.Product, 0, len(mr.products)) | ||
for _, product := range mr.products { | ||
products = append(products, product) | ||
} | ||
return products, nil | ||
} | ||
|
||
// GetByID searches for a product based on it's ID | ||
func (mpr *MemoryProductRepository) GetByID(id uuid.UUID) (aggregate.Product, error) { | ||
if product, ok := mpr.products[uuid.UUID(id)]; ok { | ||
return product, nil | ||
} | ||
return aggregate.Product{}, product.ErrProductNotFound | ||
} | ||
|
||
// Add will add a new product to the repository | ||
func (mpr *MemoryProductRepository) Add(newprod aggregate.Product) error { | ||
mpr.Lock() | ||
defer mpr.Unlock() | ||
|
||
if _, ok := mpr.products[newprod.GetID()]; ok { | ||
return product.ErrProductAlreadyExist | ||
} | ||
|
||
mpr.products[newprod.GetID()] = newprod | ||
|
||
return nil | ||
} | ||
|
||
// Update will change all values for a product based on it's ID | ||
func (mpr *MemoryProductRepository) Update(upprod aggregate.Product) error { | ||
mpr.Lock() | ||
defer mpr.Unlock() | ||
|
||
if _, ok := mpr.products[upprod.GetID()]; !ok { | ||
return product.ErrProductNotFound | ||
} | ||
|
||
mpr.products[upprod.GetID()] = upprod | ||
return nil | ||
} | ||
|
||
// Delete remove an product from the repository | ||
func (mpr *MemoryProductRepository) Delete(id uuid.UUID) error { | ||
mpr.Lock() | ||
defer mpr.Unlock() | ||
|
||
if _, ok := mpr.products[id]; !ok { | ||
return product.ErrProductNotFound | ||
} | ||
delete(mpr.products, id) | ||
return nil | ||
} |
Oops, something went wrong.