TDD & DDD from the Ground Up Live Coding by Chris Simon

  Рет қаралды 26,489

Devoxx

Devoxx

21 күн бұрын

Are you interested in using Domain-Driven Design (DDD) to create maintainable and scalable software, but not sure how to get started? Or perhaps you've heard that DDD is only suitable for complex domains - and when starting out, you're not sure if your project will need it?
Join me for a live coding demonstration that will show you how to apply Test-Driven Development (TDD) from the very beginning of a project so you can bring DDD in when you need it.
We'll start with the simplest possible implementation - a basic CRUD system to help a university handle student enrolments. We'll gradually add more complex requirements, such as the need to ensure courses don't become over-enrolled - which will prompt us to do some code-smell refactoring, strangely enough arriving at things that start to look like the DDD tactical patterns of repositories, aggregates and domain services.
In implementing these requirements, inspiration will strike! What if the model were changed - what if we allowed all enrolments and then allocated resources to the most popular courses as required so we never have to prevent a student from enrolling? We'll now see how the TDD tests and the neatly refactored domain models make it much easier to embark on this dramatic change - in other words, how much more maintainable our DDD codebase has become.

Пікірлер: 52
@TechTalksWeekly
@TechTalksWeekly 19 күн бұрын
Chris is an excellent speaker and this talk has been featured in the last issue of Tech Talks Weekly newsletter 🎉 Congrats!
@devenwen4368
@devenwen4368 14 күн бұрын
Great talk, this talk is a excellent as a crash course for TDD.
@ChrisSimonAu
@ChrisSimonAu 13 күн бұрын
Thank you!
@khmarbaise
@khmarbaise 19 күн бұрын
Really good and excelent talk. In particular explaining in very simple and easy way how event sourcing is working etc.
@sauliustb
@sauliustb 12 күн бұрын
Great video. Not only the how of starting with TDD in a conceivable situation, but also the why of not only TDD, and the next step, but also the size of the steps.
@ChrisSimonAu
@ChrisSimonAu 12 күн бұрын
Thank you!
@TheCodeConnoisseur
@TheCodeConnoisseur 19 күн бұрын
Great demo
@ChrisSimonAu
@ChrisSimonAu 19 күн бұрын
Thanks!
@maksimmuruev423
@maksimmuruev423 2 күн бұрын
How contextive is different to just comments or javadoc comments? Looks like the same goal can be achieved here.
@ChrisSimonAu
@ChrisSimonAu Күн бұрын
Comments such as JavaDoc are tied to the class so it will only show in the hover popup when you use that specific class - e.g if you define Student on the Student entity class you'll only see it when using the classname. Contextive is looking for the text string. This means it will show the definition of the word in more places such as other classes that use the word (e.g StudentController), properties and methods (e.g StudentId, RegisterStudent) and even other languages like js or ts if you have a frontend RegisterStudentForm component. For this reason I recommend contextive is just for documenting domain concepts, not implementation details. JavaDoc should be for how to use the class.
@LjuboThePro
@LjuboThePro 16 күн бұрын
Great talk!
@ChrisSimonAu
@ChrisSimonAu 16 күн бұрын
Thanks!
@sagarrout007
@sagarrout007 16 күн бұрын
Nice stuff. Learnt first time breaking things makes more sense rather than doing in one go, as you rightly expressed about balance between testing vs implementation. I have one question,. how do you see REST (Resource) + DTO vs Domain... how they go or should go from your view ?
@ChrisSimonAu
@ChrisSimonAu 16 күн бұрын
Thanks! Great question - I think a DTO to define the payload of a REST resource request or response is a very useful pattern for at least 2 scenarios: 1. You want to have some private data on the domain model that you don't want exposed via the API 2. You want to ensure a consistent contract on the API but have the freedom to change your domain model. In this demo, it starts with just sending the domain model as the API response. However, the tests include their own copy of the API payload - so effectively, a DTO specifying the API Consumer expectation. If you modify the domain model such that it breaks the test, then that could be solved at that time by creating a DTO on the application isde and setting up a mapping.from the domain model. To further protect things, you should ensure the tests use 'strict' deserialisation - e.g. unexpected fields cause deserialisation failure.
@CheeseStickzZ
@CheeseStickzZ 3 күн бұрын
TDD is amazing! It only takes 3x as long to get out some working code! It gets 3x as expensive, stakeholders start complaining, but let's do full TDD anyway!
@ChrisSimonAu
@ChrisSimonAu 3 күн бұрын
That has not been my experience but if it has been yours then of course, by all means don't do TDD. However, I'm curious what you mean by "Full TDD". Does the style of TDD I show in this talk count as "Full TDD" to you?
@CheeseStickzZ
@CheeseStickzZ 3 күн бұрын
@@ChrisSimonAu No, full TDD also accounts for the complex business cases the business team comes up with. If you're going to pre write a test for every single use case then get all of those done then have changing business requirements, you'll spend your entire existence writing and refactoring tests and good lucky hitting deadlines
@ChrisSimonAu
@ChrisSimonAu 3 күн бұрын
@@CheeseStickzZ ok, if "Full TDD" is where you pre-write all the tests (for what its worth i dont know anybody who does that...), what do you call the style of TDD in this talk where you don't pre-write any tests?
@CheeseStickzZ
@CheeseStickzZ 3 күн бұрын
@@ChrisSimonAu Not sure I've never written tests before the implementation, I'm just mentioning that even if you do there are still complex use cases/scenarios to test for even afterwards, and in an environment where business requirements change often it gets very cumbersome to rewrite both implementation and tests over and over again. Small dev team, tight deadlines.
@ChrisSimonAu
@ChrisSimonAu 2 күн бұрын
​@@CheeseStickzZyes, dealing with an environment with changing business requirements is always a challenge - personally, TDD helps me with that because ideally you only change the tests that represent requirements that actually changed. This helps ensure you haven't broken all the other functionality. If you have to change tests just because you're changing the design, then I encourage you to look for ways of writing tests that keep them less coupled to implementation. This talk has an example of this in the last 20mins where the requirements change and you can see that only a small number of tests need adjusting. The style in this talk involves writing one very small test, then one small piece of functionality. I've found this helps me ensure the tests are not coupled too tightly to implementation, and that I don't spend too much time writing tests before delivering value. Hope it helps see different ways of approaching the goal...
@TheBearTN
@TheBearTN 16 күн бұрын
Great ✨
@ChrisSimonAu
@ChrisSimonAu 16 күн бұрын
Thanks!
@user-vh6wk5eh6q
@user-vh6wk5eh6q 7 күн бұрын
Can you send me the link address of the project? thks
@ChrisSimonAu
@ChrisSimonAu 7 күн бұрын
I can't paste a link but check the qr code at 51:54
@IlyaDenisov
@IlyaDenisov 18 күн бұрын
Nice presentation. By the way, shouldn't concurrency be handled by use of proper SQL transaction isolation level (I think Repeatable Read is what you need in that case)? And then if transaction commit failed because of the isolation level (meaning that some of the values you've read has been modified concurrently), you just execute your controller method (or a method in the enrollment service if all of the logic is there) once again from scratch.
@ChrisSimonAu
@ChrisSimonAu 18 күн бұрын
There are a number of technical mechanisms for "solving" the concurrency issue - transaction isolation level is potentially one, as are a variety of pessimistic and optimistic concurrency controls. They each have their tradeoffs, balancing throughput and waittimes. If one of those mechanisms was used, then as you point out, whichever way the concurrent activity is detected, the operation could be retried in a new transaction which would protect the business rule. In this case, cause one of the students to be told their enrolment was no longer possible. The point of that part of the talk wasn't really the particular mechanism but more to highlight that a deeper understanding of the domain can remove the concurrency risk all together - and lead to a better outcome as students are less likely to experience their enrolments failing and the education provider can service more students overall.
@IlyaDenisov
@IlyaDenisov 18 күн бұрын
@@ChrisSimonAu Sure thing :) I just wanted to share that locking / versioning doesn't have to be implemented by a programmer (as this implementation will then have to be maintained), instead - features of the components that are already part of the system (database) could be used to achieve the desired behavior.
@alexsmart2612
@alexsmart2612 18 күн бұрын
​@@IlyaDenisov Yes, it is possible to use database as your concurrency control mechanism and that is typically fine for small projects and prototypes, but if your application scales, you run into problems. For instance, you start having some long running transactions and because you have put everything inside "@Transactional", you soon enough run into lock wait timeouts and deadlocks. Also, think about what happens if at some point, you want to introduce some caching into your application, so that not every single request hits the database. Now that task has become significantly more difficult because you were relying on your reads and updates being in the same transaction boundary for your application's correctness. I am not saying that your approach is always incorrect, but keep in mind that every design decision has consequences.
@IlyaDenisov
@IlyaDenisov 17 күн бұрын
​@@alexsmart2612 Good point. Yet I think this aspect (concurrency handling for the feature) should evolve incrementally in the same way as other aspects of the app, so that initial implementation that is suitable for the described logic and project state, could be as simple as transaction isolation levels. That kind-of resonates with the idea behind TDD approach highligthed by Chris in the video - engineer will benefit from maintaining a balance by doing only a small reasonable step at a time, instead of trying to build THE ULTIMATE SYSTEM from the start :)
@alexsmart2612
@alexsmart2612 17 күн бұрын
​@@IlyaDenisov Once you have a million loc project with infrastructure and business logic concerns spread all over the place, it is an expensive hole to crawl yourself out of. While I completely agree with the general principle of "maintaining a balance", for me optimistic locking, keeping small transaction boundaries etc fall well within that reasonable balance. It is not all that hard to implement and maintain even for junior developers (after a brief training period). That the resulting code is typically much simpler and easier to reason about is an added bonus.
@IvanRandomDude
@IvanRandomDude 16 күн бұрын
Intellij IDEA really screams at me when I pass optional as method argument.
@ChrisSimonAu
@ChrisSimonAu 15 күн бұрын
Yes, the Java team recommends Optional is only used for return types. In return types, it is to make it explicit to the caller that they would need to handle a None case. For method arguments, they discouraged it, for a bunch of pragmatic reasons - if you google "java optional as method argument" there are some great stackoverflow discussions on the topic with for and against arguments. Personally, having familiarity in more functional languages, I'm very comfortable using a Maybe/Option type as an argument - but try to follow the Java recommendations/idiomatic approach in this demo. I actually don't recall using it as a method argument in this demo though - can you remind me where I may have done that?
@IvanRandomDude
@IvanRandomDude 15 күн бұрын
@@ChrisSimonAu 25:03
@ChrisSimonAu
@ChrisSimonAu 15 күн бұрын
​@@IvanRandomDude Great catch - thanks so much! I think the Java idiomatic thing to do here would be to not pass an Optional in, and instead just pass in a Room and have the controller look like: Course newCourse = roomRepository.findById(courseRequest.getRoomId()) .map(r -> Course.includeInCatalog(courseRequest.getName(), r)) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); Keeping the functional/monadic style separate from the entity method. I'll update my repo for future demonstrations :)
@PaulSebastianM
@PaulSebastianM 18 күн бұрын
I liked the demo but I can't help but feel that it's too much on the happy path which does not help show how TDD helps you course correct immediately when something doesn't feel right.
@cobrab1978
@cobrab1978 19 күн бұрын
I like the demo, but i have some questions. Why in your domain not appear Enrolment? And why the class Enrolment has studentId and courseId, and not has a instance of Student and a instance of Course? Thanks!!
@ChrisSimonAu
@ChrisSimonAu 18 күн бұрын
I'm not sure about the first question - there is an Enrolment entity, but it is in the Enroling vertical slice/feature folder. On the second question, I didn't have time to talk about the tradeoffs of those two approaches, but it's a good question. To start with, I find starting with IDs simpler as you can build up some of the functionality in this incremental style, even before the other entity exists. Later, when it does exist you could refactor to use a reference object instead of an ID - if it makes sense. As to why it might or might not make sense - this is a big topic that relates to transactional consistency boundaries (aka aggregates), lazy vs eager loading and read vs write models. It's hard to summarise in a youtube comment but if you google "ddd reference object vs Id" you will find much discussion about it! In particular google "vaughn Vernon aggregate design rule 3" which should turn up some papers that take an in depth look at this pattern.
@KrycekA
@KrycekA 18 күн бұрын
If you apply DDD “by the book” then it is not allowed for one aggregate root to keep a strong reference to another aggregate root. However, it is allowed to store the unique identifier of that other aggregate root (weak reference). In the demo of Chris, Student, Course and Enrollment are all separate aggregate roots I believe. It is also said that in a single transaction, your are not allowed to update multiple aggregate roots. If one would store a strong reference to another aggregate root; it would be possible to update multiple aggregate roots which thus violates the principle. If 1 aggregate is interested in changes that occur in another aggregate; you should implement domain events. The other aggregate root should have a domain event listener that reacts on those events in its own transaction. I hope that makes sense :)
@mateuszmazurczak1268
@mateuszmazurczak1268 14 күн бұрын
"Why Enrolment has studentId and courseId instead of an instance of those classes" Because if you implement with instances your changes you apply are not atomic anymore and concurrent updates will be hard. Imagine someone at the same time changes something about course (e.g. professor updating curriculum) and student at the same moment updates his personal info. When both of them try to save, it will fail! Even though their updates have nothing to do with each other. Keeping them separated and only related by id, allows for smaller changes to reason about. Which is another pro of this solution, because the example is simple right now, but all of the software will get more and more complex, have more business cases. Having them together will make it complex to reason about each of them and we have limited brain capacity
Event-Driven Architecture (EDA) vs Request/Response (RR)
12:00
Confluent
Рет қаралды 120 М.
3M❤️ #thankyou #shorts
00:16
ウエスP -Mr Uekusa- Wes-P
Рет қаралды 14 МЛН
Heartwarming: Stranger Saves Puppy from Hot Car #shorts
00:22
Fabiosa Best Lifehacks
Рет қаралды 20 МЛН
NERF WAR HEAVY: Drone Battle!
00:30
MacDannyGun
Рет қаралды 52 МЛН
Happy 4th of July 😂
00:12
Pink Shirt Girl
Рет қаралды 47 МЛН
Don't build another effin' chatbot - Web Dev Challenge S1E1
21:54
Learn With Jason
Рет қаралды 64 М.
Kafka vs. RabbitMQ vs. Messaging Middleware vs. Pulsar
4:31
ByteByteGo
Рет қаралды 77 М.
So You Think You Know Git - FOSDEM 2024
47:00
GitButler
Рет қаралды 1 МЛН
Interview with Senior JS Developer 2024 [NEW]
6:45
Programmers are also human
Рет қаралды 481 М.
Linux on Windows......Windows on Linux
23:54
NetworkChuck
Рет қаралды 237 М.
Crafting Domain-Driven Designed REST APIs - Julien Topçu - DDD Europe 2022
55:46
Domain-Driven Design Europe
Рет қаралды 13 М.
My Burnout Experience
15:20
ThePrimeTime
Рет қаралды 140 М.
Здесь упор в процессор
18:02
Рома, Просто Рома
Рет қаралды 183 М.
Опять съемные крышки в смартфонах? #cmf
0:50
После ввода кода - протирайте панель
0:18
Up Your Brains
Рет қаралды 1,1 МЛН
ИГРОВОВЫЙ НОУТ ASUS ЗА 57 тысяч
25:33
Ремонтяш
Рет қаралды 345 М.