Skip to content
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

Closed
frederikhors opened this issue Nov 16, 2020 · 20 comments
Closed

Congratulations and question #2

frederikhors opened this issue Nov 16, 2020 · 20 comments

Comments

@frederikhors
Copy link

@resotto congratulations for this amazing project!

Can I ask you how to handle db transactions across many repositories from a usecase (service).

Thanks again!

@resotto
Copy link
Owner

resotto commented Nov 16, 2020

@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.
Thus, its solution may be also the same as that of microservices, such as saga pattern.

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.

@frederikhors
Copy link
Author

frederikhors commented Nov 16, 2020

Oh, how I'd like to understand better what you said! 😄

Can you give a concrete example?

Example:

I have an "Order" usecase that:

  1. first check that the customer is not suspended (with usecase or repository "customer")
  2. then create an order on the DB (with usecase or repository "order")
  3. then subtracts the sums from the customer (with usecase or repository "money")

How to do this in the same DB transaction?

Does what I'm saying make sense?

@resotto
Copy link
Owner

resotto commented Nov 16, 2020

@frederikhors OK, I explain what I said with your example.

I think the example above means:

  1. Confirm the customer is valid
  2. Create Order
  3. Subtract the amount from the customer balance

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.
So you can save them in one DB transaction.

If you have Customer microservice and Order microservice, the example above cannot be applied to this situation because they are handled by different services.
Thus, we cannot save them in one DB transaction.

However, there are some best practices for this situation such as saga pattern or try-cancel/confirm pattern.

@frederikhors
Copy link
Author

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?

@resotto
Copy link
Owner

resotto commented Nov 17, 2020

@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.

Models Layer
Repository Layer
Usecase Layer
Delivery Layer

@resotto
Copy link
Owner

resotto commented Nov 17, 2020

@frederikhors If you have any updates, please reopen this.

@resotto resotto closed this as completed Nov 17, 2020
@frederikhors
Copy link
Author

Yes, but beyond the definition in the readme, what do you think of the files, the arrangement of the layers?

@resotto
Copy link
Owner

resotto commented Nov 17, 2020

OK, I check it now.

@resotto
Copy link
Owner

resotto commented Nov 17, 2020

@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.

@frederikhors
Copy link
Author

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?

@resotto resotto reopened this Nov 17, 2020
@resotto
Copy link
Owner

resotto commented Nov 17, 2020

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:

  • Models Layer
  • Repository Layer
  • Usecase Layer
  • Delivery Layer

Moreover, that project seems to be derived from this article, I think.
And this article also says the same layer arrangement so I think this is also not correct.

In my prediction,

  • Models Layer means the core of CA?
  • Usecase Layer means the second circle from the core of CA?
  • Delivery Layer means the third or fourth circle from the core of CA?
  • I can't imagine what Repository Layer means.

In Goilerplate, there are four layers Domain / Application / Adapter / (External) and usually we don't write much codes in external layer.
About Repository Layer in that project, it should be implemented across Domain Layer and Adapter Layer.

This instruction is helpful to understand it.

About how Goilerplate obeys Clean Architecture, please read this part.

@frederikhors
Copy link
Author

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:

  • Entities: person (user who is also a customer), products, orders.
  • The customer creates an order with a series of products that have a price.
  • The order is generated and everyone is happy.

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.

@resotto
Copy link
Owner

resotto commented Nov 17, 2020

@frederikhors OK, I understood what you want.
Please give me some time for preparing more practical and simple example of CRUD.

Thank you.

@frederikhors
Copy link
Author

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.

@resotto
Copy link
Owner

resotto commented Nov 17, 2020

OK, I'll make it.

@resotto
Copy link
Owner

resotto commented Nov 18, 2020

@frederikhors I have just written this contents. How about it?


How to start with Goilerplate

With Goilerplate, you can start your project smoothly.

For explanation, let's create simple "CR" part of CRUD of following specifications with Goilerplate.

Specifications:

  • There are three entities such as Customer, Product, and Order.
  • Order aggregates Customer and Product (Order is Aggregate Root).
  • There is only one usecase to create an order.

NOTICE:

  • For convenience, the minimum codes are shown here.
  • For convenience, there are no test codes in this explanation.

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 Entities

Secondly, 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 Interfaces

After defining entities, let's prepare their repositories in domain package.

// 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 Usecase

And 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 Implementations

After preparing the usecase, let's implement repository interfaces in adapter package.

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 Controller

Finally, 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!

@resotto resotto closed this as completed Nov 18, 2020
@frederikhors
Copy link
Author

What a show!

I took the liberty of creating a PR (#3) to start examples of this awesome project!

@resotto
Copy link
Owner

resotto commented Dec 8, 2020

please go to discussion

@resotto resotto closed this as completed Dec 8, 2020
@frederikhors
Copy link
Author

Please transform yourself this to a discussions thread.

@resotto
Copy link
Owner

resotto commented Dec 9, 2020

I think I cannot convert this topic to discussion directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants