-
-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Congratulations and question #2
Comments
@frederikhors Thank you for your praise and question! I think DB transactions across many repositories from a usecase is the same as that of each microservices. Moreover, the first solution I thought is designing the domain model and its granularity of the aggregation again, so they might be united into one aggregation root which means one repository exists. Thank you. |
Oh, how I'd like to understand better what you said! 😄 Can you give a concrete example? Example: I have an "Order" usecase that:
How to do this in the same DB transaction? Does what I'm saying make sense? |
@frederikhors OK, I explain what I said with your example. I think the example above means:
So, if you want to have the same DB transaction, you could aggregate Customer in Order like: type Customer struct {
isvalid bool
balance int
}
type Order struct {
customer Customer
}
func checkout(c Customer, amount int) {
if !c.isvalid {
return
}
o := Order{
customer: c
}
o.customer.balance -= amount
// repository.save(o)
} The code above is simplified so much but there is only one repository since Order aggregates Customer. If you have Customer microservice and Order microservice, the example above cannot be applied to this situation because they are handled by different services. However, there are some best practices for this situation such as saga pattern or try-cancel/confirm pattern. |
OK thanks. But I am still very confused. Maybe because I'm following this pattern: https://github.com/bxcodec/go-clean-arch. By the way, what do you think of that project? |
@frederikhors I read that repo's README. In my humble opinion, what is 4 "Domain" layer? Only one domain layer should exist (actually there should be 4 layers in total like Domain / Application / Adapter / External), I think.
|
@frederikhors If you have any updates, please reopen this. |
Yes, but beyond the definition in the readme, what do you think of the files, the arrangement of the layers? |
OK, I check it now. |
@frederikhors Oh, I understood why you were very confused because I was very confused too about arrangement of the layers. So I proudly recommend Goilerplate to you or anyone who wants Go in Clean Architecture. |
Ok, but now I'm confused and therefore reluctant to use goilerplate because I have that scheme in mind (which by the way seems right). Can you explain the differences between the two projects? |
frederikhors IMHO, that project you taught me is not correct about Clean Architecture. Indeed, its package structure might be different from Goilerplate, this is acceptable because this doesn't matter so much. However, in my knowledge, following layers are not according to the definition of Clean Architecture, which are written in README:
Moreover, that project seems to be derived from this article, I think. In my prediction,
In Goilerplate, there are four layers Domain / Application / Adapter / (External) and usually we don't write much codes in external layer. This instruction is helpful to understand it. About how Goilerplate obeys Clean Architecture, please read this part. |
Ok. I had already read and reread everything. I am about to start a new project and I want to choose the best ideas. However, even re-reading and reviewing the directory and file structure I cannot understand well and am very confused. Proposal: perhaps an example more similar to the usual examples used to understand an architecture would be very useful. I'm thinking for example about the classic one:
Of course, you don't have to write the business logic, that's not the point; I do NOT expect to find processes of authentication, authorization, payment, various verifications: NOTHING of all this. I would like to understand how to organize these "simple" and common CRUD situations that are present in 90% of projects. Do you agree with me? I am ready to try and "review" everything. I am happy with the project that I linked to you but I realize that maybe I'm wrong and maybe yours is better. |
@frederikhors OK, I understood what you want. Thank you. |
Yes. But again please don't spend your precious time writing business code. Maybe over time we can fix it and make it a real working product, for now, in the next few hours, even just a fake project but set correctly, set up as you see things (and how I would like to see them) would be great. |
OK, I'll make it. |
@frederikhors I have just written this contents. How about it? How to start with GoilerplateWith Goilerplate, you can start your project smoothly. For explanation, let's create simple "CR" part of CRUD of following specifications with Goilerplate. Specifications:
NOTICE:
First of all, please prepare .go files with following package layout. Package Layout.
└── internal
└── app
├── adapter
│ ├── controller.go # Controller
│ └── repository # Repository Implementation
│ ├── customer.go
│ ├── product.go
│ └── order.go
├── application
│ └── usecase # Usecase
│ └── createOrder.go
└── domain
├── customer.go # Entity
├── product.go # Entity
├── order.go # Entity
└── repository # Repository Interface
├── customer.go
├── product.go
└── order.go Define EntitiesSecondly, let's create entities, Customer, Product, and Order. // customer.go
package domain
type Customer struct {
ID string
Name string
} // product.go
package domain
type Product struct {
ID string
Price int
} // order.go
package domain
type Order struct {
ID string
Customer Customer
Product Product
} Define Repository InterfacesAfter defining entities, let's prepare their repositories in // customer.go
package repository
type ICustomer interface {
Get(id string) domain.Customer
} // product.go
package repository
type IProduct interface {
Get(id string) domain.Product
} // order.go
package repository
type IOrder interface {
Save(order Order)
} Define UsecaseAnd then, let's prepare the usecase of creating order. // createOrder.go
package usecase
import (
"domain" // simplified for convenience
"domain/repository" // simplified for convenience
)
type CreateOrderArgs struct {
CustomerID string
ProductID string
CustomerRepository repository.ICustomer
ProductRepository repository.IProduct
OrderRepository repository.IOrder
}
func CreateOrder(args CreateOrderArgs) domain.Order {
customer := args.CustomerRepository.Get(args.CustomerID)
product := args.ProductRepository.Get(args.ProductID)
order := domain.Order{
ID: "123",
Customer: customer,
Product: product,
}
args.OrderRepository.Save(order)
return order
} Define Repository ImplementationsAfter preparing the usecase, let's implement repository interfaces in However, this part is omitted here for convenience. // order.go
package repository
import (
"domain" // simplified for convenience
)
type Order struct{}
func (o Order) Save(order domain.Order) {
// omitted here for convenience
} Define ControllerFinally, let's define controller to call the usecase of creating an order. // controller.go
package adapter
import (
"repository" // simplified for convenience
"usecase" // simplified for convenience
"github.com/gin-gonic/gin"
)
var (
customerRepository = repository.Customer{}
productRepository = repository.Product{}
orderRepository = repository.Order{}
)
type Controller struct{}
func Router() *gin.Engine {
r := gin.Default()
ctrl := Controller{}
r.POST("/order", ctrl.createOrder)
return r
}
func (ctrl Controller) createOrder(c *gin.Context) {
customerID := c.Query("customerId")
productID := c.Query("productId")
args := usecase.CreateOrderArgs{
CustomerID: customerID,
ProductID: productID,
CustomerRepository: customerRepository,
ProductRepository: productRepository,
OrderRepository: orderRepository,
}
order := usecase.CreateOrder(args)
c.JSON(200, order)
} That's it! |
What a show! I took the liberty of creating a PR (#3) to start examples of this awesome project! |
please go to discussion |
Please transform yourself this to a discussions thread. |
I think I cannot convert this topic to discussion directly. |
@resotto congratulations for this amazing project!
Can I ask you how to handle db transactions across many repositories from a usecase (service).
Thanks again!
The text was updated successfully, but these errors were encountered: