This as actually a good idea, I will start working on a Hexagonal Tavern tutorial! Give me some time though, switching jobs next month and with Xmas and New years it's gonna be a few weeks! I would say that your assumption is correct if this was implemented in a Hexagonal design. Again, there are many flavors of doing this out there, but I'd say the services etc would be used in the adapters etc. I couldn't say right of the bat how I'd structure it, but I'll make sure to create a Hexagonal Tavern that is controlled as a CLI! Thank you for providing an interesting idea
Better than a lot of courses and tutorials! This is super helpful who wants to learn design pattern with practical usecase! Thanks for the effort Percy!
One thing which I like very much in your teaching style is that you 'speak' your thought process, even write comments sometimes to show what you are going to implement next. This enables this video to be viewed as a guiding excercise, one can pause it at the right time, execute the solution themselves and play again to see how you did it. Thank you.
Thank you for reaching out! I am really glad you enjoy my teaching! I am happy to hear you say that you enjoy the thought process, I do try to cover those things because I often find the idea or logic behind the code to be very useful when learning. Atleast for myself, I learn faster and remember it longer when I understand the WHY and not only the HOW. Your comment made my day! Best of luck to you out there, and smash them keyboard keys!
Great demonstration. I'm 1 hour in, and this code organization makes a lot of sense. I would recommend changing the repository names though. Inside domain/customer/repository.go, the interface can simply be called Repository. And then in domain/customer/memory/repository.go (not memory.go), you can name the concrete implementation also simply Repository. So you have interface customer.Repository, and implementation memory.Repository. Not customer.CustomerRepository and memory.MemoryRepository. That is called "stuttering", and it should always be avoided. Oh and there's a tiny race condition at 44:15. Theoretically someone could add a customer with that id between line 39 and 41. It's safer to move the Lock() before the if on line 38, and don't forget to add an Unlock() before the fmt.Errorf().
What amazing content, it was great to see all this working together in a more real example, great patterns too, it would be amazing if you could later bring more content about this, maybe implementing it in a Rest API, thank you very much!
thanks a lot for such a wonderful video! a lot of useful information, right now we're going to improve our backend architecture (along with migration from Python to Go) and I'm looking for different approaches and experience and feel like you video might help us a lot to build a new architecture.
Hello from Singapore!! Thanks for the video, it is very insightful. You mentioned in the video that this directory structure is not recommended for production use cases. Would be good if you could talk more about production.
I watched the entire video and the next one but I'm curious on what's the semantic related on how do you integrate: - an websocket/http server for having api access to a Service: is there a term in that DDD? is the way we manage repository can fit the case? - workers that process data from external sources to be mutated/transformed into our aggregate struct: the DDD show how to manage your own data but what about externals? what about long term processes? - utilities that allow you to perform meta programming like reflection on a struct to output data: does DDD is ideal for libraries that does that type of work? I have so much questions - can we do a reference between two aggregates?: for example we could have entities (temperature, coordinates, etc..) linked to an aggregate (planet) which belong to another aggregate (system) which also belong to another aggregate (galaxy). Great video btw, very instructive!
Thank you for watching! And good questions. 1. Well, I would say that the API is the Service. 2. I am not sure I follow the question really 3. I think DDD Fits this approach also, the meta programming is part of the logic inside a Domain, its just a way of processing logic, wouldnt matter if its regular programming or meta programming. 4. Yes, you can!
Wonderful. Thanks for the video. The issue I am facing is about cross-cutting concerns like logging and tracing. Not sure whether I am doing the right structuring or not with them. Bought the blue book. Let's see if it can help with those.
Such a great video, one thing I noticed is that you some times do "package _test" and "package " for the test files, is there a reason to add or omit the suffix _test ?
Hey, sorry for a late answer. Yes in fact there is. Any test that you use the same Package name for will have access to everything internally, (lower case stuff) If you add the _test suffix it will test like an External Package. Hope that makes sense!
i'm currently at the creating CustomerRepository and I see you don't use pointers when returning aggregate.Customer or accepting it as arguments, the struct could have a lot of data, like a lot of transactions and products, why didn't you use pointer here? How do you decide when or where to use pointers in go?
The base rule is to use pointer whenever you expect the function or the code that accepts the return value to modify the values. Since in this case we don't modify the data, no need for a pointer. When I code in Go I often overuse pointers since I want to have an easy time to Nil check though. But I've noticed that in large code bases pointers are also sometimes confusing, values changing a little bit of everywhere etc. So in this case I decided to go for the Non Pointer. True, if the structs are very big passing a pointer can have the added feature of performance gains due to avoiding copies. Though with all in mind, I do like the standard rule, is values expected to be modified, if not, regular copy, otherwise pointer. Makes the code very easy to understand.
I'm a bit late to the party, but just finished the vid and had a question about the config pattern you used to hook up the repositories to the services. with that implementation, wouldn't you need to specify the repositories on EVERY implementation of a service? wouldn't it be better to call those configs in the init of a service to avoid having to change the config in every place that you use that service?
That would be possible. I'd rather use a constructor func to initialize it and manage it that way though, I am opposed to Init functions for most usecases. But you can narrow it down with a constructor func that uses some Default repositories and configs. Init funcs have their place but I find it seldom a good approach. Init functions are also great at causing unexpected bugs and frustation in my experience
@@programmingpercy I like the default repositories approach. My concern was mostly around larger codebases where a service is used in a lot of places. If you need to change the repository, you’ll need to update all those areas where the service is being used, which defeats the point of having the config approach to begin with as it’ll be a nightmare to update all those instances.
i'm watching it and i wanna say thanks in advance: clear and simple. But i'm asking myself in Repository Pattern chapter (about 36:00 - 40:00) why checking if person or customers map exist when you get this aggregate by factory method that assure properly init of this entities?
Good question! Extra safety and habbit I guess. The factory does prevent it, but somebody can skip using the factory etc. Not a strong reason, but I think that was why I added it.
Hi, what if you need to update multiple orders of an user in a single transaction? the steps would be: begin tx, getUser, updateOrders, end tx but due to repositories we can't not start a lets say, postgres/mysql transaction. How can we do this in case we need a transaction through multiple repositores?
Good question Alonso! One thing that I don't show here which I commonly do is to pass in a Database conn in the Repository Factory Func. NewMysqlUserDB(db *db.Conn) Etc, that allows me to reuse the same connection if I have multiple Databases which are the same and I need to limit the amount of connections. That Db Conn can then be stored in the Repository struct and used for creating a TX
Just realized that doesn't help you. You could pass in a Ctx and use the same Root CTX between the repositories and maybe Pass the TX through there, but I've never been a fan of that myself. Either the domain scope should be altered if they have such an Relation. Another option could be to have the accept an TX of course, Pass in the TX in both Funcs simply and close it afterwards. That would be the most simple solution. It would break coupling since it would assume both repositories uses the same db engine I suppose. If there is such a coupling and needs to be done that way, I'd say creating the TX I'n a service which holds both repositories and pass the TX in the Funcs from the service, and then end tx in service is the most clean solution.
Thank you for the great video and lecture. It is really amazing, I have learned a lot from you. Anyway, do you mind also to share how to handle transactional is repository pattern and DDD perspective? Thanks again
Cycle dependency was an issue at first for me in Go, but after a few months of go I stopped seeing that issue, its about a mindset. Services can be inside the same package, its the domains that should be separated and a Domain should preferably never need to import another domain
I am just not very happy with the Globally shared Entities approach. But I do it in some projects when an entity is shared across the entire scope of the project. I do like having entities as part of their respective Domain. If I am building a GitHub Domain, then the github related entities are found in that domain
Yes sir! The first video code can be found here: github.com/percybolmer/ddd-go Second video is focused on cleaning the structure up, since the first video uses a weird setup to emphasize the theory and ideas. That code is in a second brach in the same repo github.com/percybolmer/ddd-go/tree/clean-architecture I hope this helps you
Cool explanation, how ever this is just crud, where will you add busines rules for instance, imagine there is a rule to not allow orders after 3pm or only allow to save only one beer by order I mean business rules not crud rules.. where will add those?
interesting. At the very first start point, you exposed an idea about language use case to call same things differently with example by analogy as client to be also a drinker. By the facts, they are not the same thining of the same people as long as people doesn't react the same in different context. Ok, it is the same person id, but not the same idea of this person now. The bankster is looking for his money as a client, but the wine manufactor is looking at him as someone that will love his wine (the underground target is not the same, and because of that, depend of who is controlled the activity, the experience of the people will not be the same with a bankster and a wine manufactor). So, even if i do understand your point to teach domain driven design at the very first point, we can not delete that most of the time, people forget that they has been called differently because the target is very different and that is the power of a language to use the correct appelation depend of: "the context" (so, it is the school role to make student understand that crucial point that make a big difference between a dumb tool's guy society and an intelligent society). The context is the one than help to define the correct word to use to define a subject (it is lexicaly correct to understand this about language). Also, there is some "generic", that can embed all of them. For example, a client is a drinker or what ever that can paid for something, but all of them are humans (i think animals will not be accepted to drink or to paid for the drink too). It is all about to understand layer up to abstraction that exist due to context and motivated target. Let's said that this way we name things would also influence our own mind thinking the way we are thinking this topic about. And that is all about humans relationship and project orientation is. What i said is that... who is the boss ? Who is the one that should define the orientation of the project if not the owner of the project ? Is it the man that borrow the monnaie or is it the man that know to do the job and his the owner because of that ? Let me tell you an example that describe our low level society it is becoming right now because of genereic inplace of anything without conscience (because to not understand that point): More and more, now, in hospital, due to free international crazy market , people injured or seek are becoming: clients. And that is why they are not happy in this hospital who has no human skills as target at the very frst place. Peoople injured are no more happy due to the very low quality of treatment, and employed people are also not happy due to the treatment they have from the hospital that is becoming an other room for businessmen, instead to still be some valuable technicians, they become diplpomate personal, with lower and lower skills by the fact, with more an dmore pressure about time to treat a... client (or at least, a society of dumb that see only generic or there own business word to be the absolute priority, out of any cultural context, out of any politic context, out of any real life context and then, because of that, out of any valuable morality context). You can not make an adapter/wrapper between business and medecine or professional . Yeah... you can do it, but it is useless from the first start point to the end one.
First off, Thank you for watching and taking the time to discuss this. And I think that we shouldn't overthink this too much. It's all about finding common ground and a good enough abstraction that fits the model of the business scope. Although you have an interesting point.
may be also need mutex to read map? here's chat gpt's example package main import ( "sync" ) type SafeMap struct { sync.Mutex Map map[string]string } func main() { // create a new safe map safeMap := SafeMap{Map: make(map[string]string)} // use the map in multiple goroutines go safeMap.Write("key1", "value1") go safeMap.Write("key2", "value2") go safeMap.Read("key1") go safeMap.Read("key2") } func (sm *SafeMap) Write(key string, value string) { sm.Lock() defer sm.Unlock() sm.Map[key] = value } func (sm *SafeMap) Read(key string) string { sm.Lock() defer sm.Unlock() return sm.Map[key] }
I am just 1h into the video and I'm in awe at how good of a lecturer you are. One does not often find this quality in a person. Keep up the good work!
So, first of all, thank you for this lecture. I really enjoyed it and learned a lot, as I struggled to get the bigger picture of DDD - until now!
Thank you very much for saying those kind words, it warms my heart to hear!
I am super happy you enjoyed it
This as actually a good idea, I will start working on a Hexagonal Tavern tutorial!
Give me some time though, switching jobs next month and with Xmas and New years it's gonna be a few weeks!
I would say that your assumption is correct if this was implemented in a Hexagonal design.
Again, there are many flavors of doing this out there, but I'd say the services etc would be used in the adapters etc.
I couldn't say right of the bat how I'd structure it, but I'll make sure to create a Hexagonal Tavern that is controlled as a CLI!
Thank you for providing an interesting idea
Better than a lot of courses and tutorials! This is super helpful who wants to learn design pattern with practical usecase! Thanks for the effort Percy!
One thing which I like very much in your teaching style is that you 'speak' your thought process, even write comments sometimes to show what you are going to implement next. This enables this video to be viewed as a guiding excercise, one can pause it at the right time, execute the solution themselves and play again to see how you did it. Thank you.
Thank you for reaching out! I am really glad you enjoy my teaching!
I am happy to hear you say that you enjoy the thought process, I do try to cover those things because I often find the idea or logic behind the code to be very useful when learning.
Atleast for myself, I learn faster and remember it longer when I understand the WHY and not only the HOW.
Your comment made my day!
Best of luck to you out there, and smash them keyboard keys!
Great demonstration. I'm 1 hour in, and this code organization makes a lot of sense. I would recommend changing the repository names though. Inside domain/customer/repository.go, the interface can simply be called Repository. And then in domain/customer/memory/repository.go (not memory.go), you can name the concrete implementation also simply Repository. So you have interface customer.Repository, and implementation memory.Repository. Not customer.CustomerRepository and memory.MemoryRepository. That is called "stuttering", and it should always be avoided. Oh and there's a tiny race condition at 44:15. Theoretically someone could add a customer with that id between line 39 and 41. It's safer to move the Lock() before the if on line 38, and don't forget to add an Unlock() before the fmt.Errorf().
This is the best video I've seen on DDD with go. Thankyou!
Thanks for sharing this. It literally elevates my unstanding and usage of Golang to another level. 😄
Awesome, we need a FULL COURSE!
What amazing content, it was great to see all this working together in a more real example, great patterns too, it would be amazing if you could later bring more content about this, maybe implementing it in a Rest API, thank you very much!
Thanks for your kind words!
This was SO good, thank you! You have help put a bunch of design patterns together for me.
Amazing. This is exactly what i'm looking for. Greate video, super clear, thank you!
Awesome Percy, wow you are a wonderful lecturer!!! Great Content by the way... Enjoyed & Learned a Lot. Thank you
Thank you for the kind words, encourages me
thanks a lot for such a wonderful video! a lot of useful information, right now we're going to improve our backend architecture (along with migration from Python to Go) and I'm looking for different approaches and experience and feel like you video might help us a lot to build a new architecture.
Thank you for making these tutorials!!
Glad you enjoyed!
This was an amazing DDD tutorial. Just one funny thing was the spelling of peanuts as “peenuts” in your unit tests 😂.
Special kind of nuts, and thanks!
@programmingpercy
Aren't your customer and product domains creating separate connections to the same db ? (can be seen around 1:50:00)
Nice course, very helpful, thanks
You're amazing Percy, keep going!
Thank you!
Hello from Singapore!! Thanks for the video, it is very insightful. You mentioned in the video that this directory structure is not recommended for production use cases. Would be good if you could talk more about production.
There is a whole video about that in the second part!
Thanks for this Mr.Percy
I watched the entire video and the next one but I'm curious on what's the semantic related on how do you integrate:
- an websocket/http server for having api access to a Service: is there a term in that DDD? is the way we manage repository can fit the case?
- workers that process data from external sources to be mutated/transformed into our aggregate struct: the DDD show how to manage your own data but what about externals? what about long term processes?
- utilities that allow you to perform meta programming like reflection on a struct to output data: does DDD is ideal for libraries that does that type of work?
I have so much questions
- can we do a reference between two aggregates?: for example we could have entities (temperature, coordinates, etc..) linked to an aggregate (planet) which belong to another aggregate (system) which also belong to another aggregate (galaxy).
Great video btw, very instructive!
Thank you for watching! And good questions.
1. Well, I would say that the API is the Service.
2. I am not sure I follow the question really
3. I think DDD Fits this approach also, the meta programming is part of the logic inside a Domain, its just a way of processing logic, wouldnt matter if its regular programming or meta programming.
4. Yes, you can!
Wonderful. Thanks for the video.
The issue I am facing is about cross-cutting concerns like logging and tracing. Not sure whether I am doing the right structuring or not with them. Bought the blue book. Let's see if it can help with those.
Great materials Percy! 🙂
Thanks Milosz!
Transaction Value Object be like: Am I a joke for you?
Thank you for this Great Content!!
Such a great video, one thing I noticed is that you some times do "package _test" and "package " for the test files, is there a reason to add or omit the suffix _test ?
Hey, sorry for a late answer. Yes in fact there is.
Any test that you use the same Package name for will have access to everything internally, (lower case stuff)
If you add the _test suffix it will test like an External Package.
Hope that makes sense!
@@programmingpercy It does, and also solves many of my issues :D Thanks
i'm currently at the creating CustomerRepository and I see you don't use pointers when returning aggregate.Customer or accepting it as arguments, the struct could have a lot of data, like a lot of transactions and products, why didn't you use pointer here? How do you decide when or where to use pointers in go?
The base rule is to use pointer whenever you expect the function or the code that accepts the return value to modify the values.
Since in this case we don't modify the data, no need for a pointer.
When I code in Go I often overuse pointers since I want to have an easy time to Nil check though.
But I've noticed that in large code bases pointers are also sometimes confusing, values changing a little bit of everywhere etc.
So in this case I decided to go for the Non Pointer.
True, if the structs are very big passing a pointer can have the added feature of performance gains due to avoiding copies.
Though with all in mind, I do like the standard rule, is values expected to be modified, if not, regular copy, otherwise pointer.
Makes the code very easy to understand.
great material, thanks a lot for your time!
You're welcome
I'm a bit late to the party, but just finished the vid and had a question about the config pattern you used to hook up the repositories to the services. with that implementation, wouldn't you need to specify the repositories on EVERY implementation of a service? wouldn't it be better to call those configs in the init of a service to avoid having to change the config in every place that you use that service?
That would be possible.
I'd rather use a constructor func to initialize it and manage it that way though, I am opposed to Init functions for most usecases.
But you can narrow it down with a constructor func that uses some Default repositories and configs.
Init funcs have their place but I find it seldom a good approach. Init functions are also great at causing unexpected bugs and frustation in my experience
@@programmingpercy I like the default repositories approach.
My concern was mostly around larger codebases where a service is used in a lot of places. If you need to change the repository, you’ll need to update all those areas where the service is being used, which defeats the point of having the config approach to begin with as it’ll be a nightmare to update all those instances.
Should the aggregate directory be in the domain directory?
shouldn't the products field in the aggregate.Customer struct just be []uuid instead of []*entity.Item?
What about generated code (like oapi-codegen)? Then my whole openapi spec is generated into one file. How one should deal with it?
you saved my life
Thank you, I think saving your life is a bit exaggerated, but I am happy to hear this helped you!
i'm watching it and i wanna say thanks in advance: clear and simple.
But i'm asking myself in Repository Pattern chapter (about 36:00 - 40:00) why checking if person or customers map exist when you get this aggregate by factory method that assure properly init of this entities?
Good question!
Extra safety and habbit I guess. The factory does prevent it, but somebody can skip using the factory etc.
Not a strong reason, but I think that was why I added it.
Hi, what if you need to update multiple orders of an user in a single transaction? the steps would be: begin tx, getUser, updateOrders, end tx but due to repositories we can't not start a lets say, postgres/mysql transaction. How can we do this in case we need a transaction through multiple repositores?
Good question Alonso!
One thing that I don't show here which I commonly do is to pass in a Database conn in the Repository Factory Func.
NewMysqlUserDB(db *db.Conn)
Etc, that allows me to reuse the same connection if I have multiple Databases which are the same and I need to limit the amount of connections.
That Db Conn can then be stored in the Repository struct and used for creating a TX
Just realized that doesn't help you.
You could pass in a Ctx and use the same Root CTX between the repositories and maybe Pass the TX through there, but I've never been a fan of that myself.
Either the domain scope should be altered if they have such an Relation.
Another option could be to have the accept an TX of course, Pass in the TX in both Funcs simply and close it afterwards. That would be the most simple solution.
It would break coupling since it would assume both repositories uses the same db engine I suppose.
If there is such a coupling and needs to be done that way, I'd say creating the TX I'n a service which holds both repositories and pass the TX in the Funcs from the service, and then end tx in service is the most clean solution.
Thank you for the great video and lecture. It is really amazing, I have learned a lot from you. Anyway, do you mind also to share how to handle transactional is repository pattern and DDD perspective? Thanks again
Sir make a complete series on E-Commerce.
why the packages is not organized by business content but DDD concepts?
How do you handle cycle dependency when some services calling each other mutually?
Cycle dependency was an issue at first for me in Go, but after a few months of go I stopped seeing that issue, its about a mindset.
Services can be inside the same package, its the domains that should be separated and a Domain should preferably never need to import another domain
why wouldn't you use a package for entities? and what would you do instead?
I am just not very happy with the Globally shared Entities approach.
But I do it in some projects when an entity is shared across the entire scope of the project.
I do like having entities as part of their respective Domain. If I am building a GitHub Domain, then the github related entities are found in that domain
Do you have a repo with this code?
Yes sir!
The first video code can be found here:
github.com/percybolmer/ddd-go
Second video is focused on cleaning the structure up, since the first video uses a weird setup to emphasize the theory and ideas.
That code is in a second brach in the same repo
github.com/percybolmer/ddd-go/tree/clean-architecture
I hope this helps you
@@programmingpercy Thanks!
Cool explanation, how ever this is just crud, where will you add busines rules for instance, imagine there is a rule to not allow orders after 3pm or only allow to save only one beer by order I mean business rules not crud rules.. where will add those?
Good question!
I'd add that in the Service or the Aggregates.
Dope 💩
do you live in paderborn or like all german houses look the same lol XD
Haha, sorry, I don't even love in Germany!
I am living in Sweden, so pretty close though.
Peenuts.
Haha yeah, my bad xD
Also, a tip: don't ever order peenuts in a bar, you might get a real chocker
interesting. At the very first start point, you exposed an idea about language use case to call same things differently with example by analogy as client to be also a drinker. By the facts, they are not the same thining of the same people as long as people doesn't react the same in different context.
Ok, it is the same person id, but not the same idea of this person now. The bankster is looking for his money as a client, but the wine manufactor is looking at him as someone that will love his wine (the underground target is not the same, and because of that, depend of who is controlled the activity, the experience of the people will not be the same with a bankster and a wine manufactor).
So, even if i do understand your point to teach domain driven design at the very first point, we can not delete that most of the time, people forget that they has been called differently because the target is very different and that is the power of a language to use the correct appelation depend of: "the context" (so, it is the school role to make student understand that crucial point that make a big difference between a dumb tool's guy society and an intelligent society). The context is the one than help to define the correct word to use to define a subject (it is lexicaly correct to understand this about language). Also, there is some "generic", that can embed all of them. For example, a client is a drinker or what ever that can paid for something, but all of them are humans (i think animals will not be accepted to drink or to paid for the drink too). It is all about to understand layer up to abstraction that exist due to context and motivated target.
Let's said that this way we name things would also influence our own mind thinking the way we are thinking this topic about.
And that is all about humans relationship and project orientation is. What i said is that... who is the boss ? Who is the one that should define the orientation of the project if not the owner of the project ? Is it the man that borrow the monnaie or is it the man that know to do the job and his the owner because of that ?
Let me tell you an example that describe our low level society it is becoming right now because of genereic inplace of anything without conscience (because to not understand that point):
More and more, now, in hospital, due to free international crazy market , people injured or seek are becoming: clients. And that is why they are not happy in this hospital who has no human skills as target at the very frst place. Peoople injured are no more happy due to the very low quality of treatment, and employed people are also not happy due to the treatment they have from the hospital that is becoming an other room for businessmen, instead to still be some valuable technicians, they become diplpomate personal, with lower and lower skills by the fact, with more an dmore pressure about time to treat a... client (or at least, a society of dumb that see only generic or there own business word to be the absolute priority, out of any cultural context, out of any politic context, out of any real life context and then, because of that, out of any valuable morality context).
You can not make an adapter/wrapper between business and medecine or professional . Yeah... you can do it, but it is useless from the first start point to the end one.
First off, Thank you for watching and taking the time to discuss this.
And I think that we shouldn't overthink this too much. It's all about finding common ground and a good enough abstraction that fits the model of the business scope.
Although you have an interesting point.
may be also need mutex to read map? here's chat gpt's example
package main
import (
"sync"
)
type SafeMap struct {
sync.Mutex
Map map[string]string
}
func main() {
// create a new safe map
safeMap := SafeMap{Map: make(map[string]string)}
// use the map in multiple goroutines
go safeMap.Write("key1", "value1")
go safeMap.Write("key2", "value2")
go safeMap.Read("key1")
go safeMap.Read("key2")
}
func (sm *SafeMap) Write(key string, value string) {
sm.Lock()
defer sm.Unlock()
sm.Map[key] = value
}
func (sm *SafeMap) Read(key string) string {
sm.Lock()
defer sm.Unlock()
return sm.Map[key]
}