SOLID Architecture: Slices not Layers
This is a modal window.
Das Video konnte nicht geladen werden, da entweder ein Server- oder Netzwerkfehler auftrat oder das Format nicht unterstützt wird.
Formale Metadaten
Titel |
| |
Alternativer Titel |
| |
Serientitel | ||
Anzahl der Teile | ||
Autor | ||
Lizenz | CC-Namensnennung - keine kommerzielle Nutzung - Weitergabe unter gleichen Bedingungen 3.0 Unported: Sie dürfen das Werk bzw. den Inhalt zu jedem legalen und nicht-kommerziellen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen und das Werk bzw. diesen Inhalt auch in veränderter Form nur unter den Bedingungen dieser Lizenz weitergeben | |
Identifikatoren | 10.5446/50483 (DOI) | |
Herausgeber | ||
Erscheinungsjahr | ||
Sprache |
Inhaltliche Metadaten
Fachgebiet | ||
Genre | ||
Abstract |
|
00:00
WhiteboardStereometrieArchitektur <Informatik>ComputersicherheitWeb-SeiteSoftwareentwicklerPhysikalisches SystemCOMBildschirmmaskeApp <Programm>PunktTouchscreenCodeFahne <Mathematik>Domain <Netzwerk>Projektive EbeneZentrische StreckungAggregatzustandKomplexes SystemSchreib-Lese-KopfOpen SourceBitGruppenoperationTabelleGeradeKartesische KoordinatenGebäude <Mathematik>Architektur <Informatik>Komplex <Algebra>Dienst <Informatik>Coxeter-GruppeVerschlingungSchnittmengeMultiplikationsoperatorDokumentenserverZahlenbereichProzess <Informatik>BenutzerbeteiligungProgrammfehlerSchreiben <Datenverarbeitung>Data MiningGüte der AnpassungSystemplattformMUDTwitter <Softwareplattform>LoginComputeranimation
04:27
Dienst <Informatik>Proxy ServerComputersicherheitDienst <Informatik>Kartesische KoordinatenAbstraktionsebeneVisualisierungArithmetisches MittelKomplex <Algebra>Web ServicesProjektive EbeneArchitektur <Informatik>Proxy ServerEreignishorizontQuick-SortEinfügungsdämpfungComputeranimation
05:52
Proxy ServerDienst <Informatik>ComputersicherheitInformationsspeicherungMathematische LogikDatenbankZeitbereichObjekt <Kategorie>DokumentenserverInterface <Schaltung>BootenZeichenketteApp <Programm>Charakteristisches PolynomIdempotentAbfrageKartesische KoordinatenBenutzerbeteiligungProxy ServerDokumentenserverKnotenmengeDienst <Informatik>Projektive EbeneAuswahlaxiomMathematikFramework <Informatik>VollständigkeitPunktspektrumArchitektur <Informatik>Physikalisches SystemBitrateMetropolitan area networkMultiplikationsoperatorMathematische LogikObjekt <Kategorie>EinsStereometrieGewicht <Ausgleichsrechnung>QuaderRobotikSichtenkonzeptInformationsspeicherungTechnische InformatikGebäude <Mathematik>Elektronische PublikationKomplexes SystemInformatikCodeFunktionalAusnahmebehandlungGüte der AnpassungResultantePatch <Software>Reelle ZahlGamecontrollerDatenbankKlasse <Mathematik>BenutzeroberflächeUnternehmensmodellArithmetisches MittelGrenzschichtablösungDifferenteVersionsverwaltungMusterspracheProzess <Informatik>Interface <Schaltung>Business ObjectSoundverarbeitungAbfrageProdukt <Mathematik>GeradeSoftwareentwicklerGatewayCASE <Informatik>HauptidealExogene VariableZentrische StreckungServerRegulärer GraphTorusTouchscreenMaschinenschreibenAlgorithmische ProgrammierspracheMinimumMereologieGrundraumFigurierte ZahlSchnittmengeAbstraktionsebeneTesselationEinfache GenauigkeitInnerer PunktDomain <Netzwerk>IdempotentQuellcodeProgram SlicingSinusfunktionÜberlagerung <Mathematik>Objektrelationale AbbildungLie-GruppeSchreiben <Datenverarbeitung>Computeranimation
15:30
DokumentenserverDatenbankUnternehmensmodellInterface <Schaltung>SinusfunktionInklusion <Mathematik>AbfrageSynchronisierungIndexberechnungSystemverwaltungSichtenkonzeptGruppenoperationFunktion <Mathematik>Ein-AusgabeSichtenkonzeptArchitektur <Informatik>Physikalisches SystemVariableGamecontrollerKartesische KoordinatenProgrammfehlerMathematikBildschirmmaskeAggregatzustandKategorie <Mathematik>SchnelltasteFigurierte ZahlAbfrageReelle ZahlQuick-SortFunktion <Mathematik>ZeichenketteDemoszene <Programmierung>ParametersystemFramework <Informatik>Data DictionaryEinfache GenauigkeitDatensatzUnternehmensmodellKomponententestBenutzeroberflächeSoftwareschwachstelleObjekt <Kategorie>GruppenoperationDifferenteFahne <Mathematik>CASE <Informatik>SchnittmengeMusterspracheProgrammierungDatenfeldAbgeschlossene MengeSoftwareentwicklerUmwandlungsenthalpieCodeGeradeVersionsverwaltungMixed RealityGebäude <Mathematik>MultiplikationsoperatorRefactoringDatenbankObjektrelationale AbbildungMailing-ListeBitMathematische LogikEin-AusgabeAssoziativgesetzHyperbelverfahrenSystemverwaltungDokumentenserverComputersicherheitHeegaard-ZerlegungAuswahlaxiomPunktspektrumRuby on RailsMatchingAbstraktionsebeneVerband <Mathematik>PunktGrundraumProzess <Informatik>Projektive EbeneInterface <Schaltung>PASS <Programm>Open SourceRechenwerkSoftwaretestInformationResultanteComputeranimation
25:08
Funktion <Mathematik>Ein-AusgabeAbfrageZeichenketteWeb-SeiteSichtenkonzeptStrom <Mathematik>DigitalfilterBenutzeroberflächeUnternehmensmodellQuick-SortDatensichtgerätKontextbezogenes SystemMessage-PassingZahlenbereichGradientSynchronisierungt-TestQuick-SortUnternehmensmodellSichtenkonzeptWeb-SeiteProjektive EbeneService providerVerschlingungFramework <Informatik>CodeAbstraktionsebeneKlasse <Mathematik>Ein-AusgabeAbfrageDatenbankt-TestDatenfeldTrennschärfe <Statistik>Regulärer GraphDeskriptive StatistikFaltung <Mathematik>Dienst <Informatik>DokumentenserverFunktion <Mathematik>Mathematische LogikGradientGeradeInformationMultiplikationsoperatorEinfache GenauigkeitPhysikalisches SystemFigurierte ZahlResultanteObjekt <Kategorie>PunktMereologieGruppenoperationKategorie <Mathematik>TouchscreenBildschirmmaskeParametersystemAutomatische HandlungsplanungExogene VariableOpen SourceMailing-ListeNatürliche ZahlBitGamecontrollerBefehl <Informatik>Automatische IndexierungZeichenketteDemoszene <Programmierung>TypentheorieReelle ZahlFortsetzung <Mathematik>CASE <Informatik>SpeicherabzugMetropolitan area networkKraftRechter WinkelTaskKünstliches LebenGrundraumWeb SiteMAPSchnelltasteProgrammbibliothekMomentenproblemBildgebendes VerfahrenFlächeninhaltRichtungFunktionale ProgrammierspracheBenutzeroberflächeComputeranimation
34:45
Kontextbezogenes SystemOvalt-TestCrosscutting ConcernsGruppenoperationSichtenkonzeptIndexberechnungZeichenketteUmsetzung <Informatik>Inhalt <Mathematik>UnternehmensmodellSkriptsprachePhysikalisches SystemVektorrechnungSpieltheorieAggregatzustandArchitektur <Informatik>Framework <Informatik>AbstraktionsebeneKategorie <Mathematik>QuaderMailing-ListeMathematikCASE <Informatik>TropfenPhysikalisches SystemValiditätt-TestUnternehmensmodellDatenbankKontextbezogenes SystemRohdatenKartesische KoordinatenOrdnung <Mathematik>ServerDifferenteDatenflussFunktionalQuick-SortAbfrageWeb-SeiteVariableBildschirmmaskeObjekt <Kategorie>SchnelltasteBrowserInformationFehlermeldungSchnittmengeEindeutigkeitSchreib-Lese-KopfTouchscreenGamecontrollerGanze FunktionProgrammbibliothekResultanteTypentheorieZahlenbereichSystemidentifikationGruppenoperationMathematische LogikRoutingEreignishorizontKlasse <Mathematik>DatenfeldNamensraumBitDienst <Informatik>GeradeDokumentenserverKnotenmengeElektronische PublikationPunktInhalt <Mathematik>CodeEinfache GenauigkeitReelle ZahlAuswahlaxiomProgram SlicingTeilbarkeitWurzel <Mathematik>DämpfungVerschiebungsoperatorMAPEnergiedichteStereometrieVierzigWort <Informatik>FokalpunktGebäude <Mathematik>Attributierte GrammatikMinimalgradComputeranimation
44:23
UnternehmensmodellSpieltheoriePhysikalisches SystemVektorrechnungAbfrageIndexberechnungZählent-TestSelbst organisierendes SystemLemma <Logik>SichtenkonzeptMultitaskingHydrostatikZeichenketteExistenzsatzKontextbezogenes SystemDateiformatSkriptspracheGamecontrollerSynchronisierungAuthentifikationAutorisierungZeitbereichUmwandlungsenthalpieArchitektur <Informatik>StereometrieCrosscutting ConcernsMaßerweiterungVererbungshierarchieService providerArchitektur <Informatik>CASE <Informatik>RoutingElektronische PublikationImplementierungBimodulService providerUnternehmensmodellFaktor <Algebra>Physikalisches SystemDienst <Informatik>GamecontrollerProgram SlicingDomain <Netzwerk>AbfrageGruppenoperationMathematische LogikWeb ServicesEntwurfsmusterDifferenteExogene VariableNormalvektorEinfache GenauigkeitTouchscreenQuick-SortZahlenbereichOpen SourceStereometrieSkriptspracheRefactoringSichtenkonzeptVerzweigendes ProgrammZusammenhängender GraphProjektive EbeneVererbungshierarchieStrategisches SpielLeckSchnittmengeWeb-SeiteDokumentenserverDatenverwaltungFramework <Informatik>Interface <Schaltung>ValiditätTypentheorieObjekt <Kategorie>ResultanteKartesische KoordinatenKlasse <Mathematik>p-BlockLastMereologieHierarchische StrukturWechselsprungMathematikBitKontextbezogenes SystemUmwandlungsenthalpieMultiplikationCodeTemplateUmkehrung <Mathematik>Prozess <Informatik>Gemeinsamer SpeicherThreadOffene MengeClientFilter <Stochastik>ZweiArithmetisches MittelGrenzschichtablösungTeilbarkeitMultiplikationsoperatorHypermediaMusterspracheHauptidealGüte der AnpassungVersionsverwaltungURLLoginAutorisierungFokalpunktNamensraumBusiness ObjectÄhnlichkeitsgeometrieSoftwareentwicklerPunktProgrammbibliothekAlgorithmische ProgrammierspracheBildschirmmaskeGebäude <Mathematik>Computeranimation
54:01
DatenbankEreignishorizontRichtungGrundraumCASE <Informatik>RoutingPhysikalisches SystemBusiness ObjectUnternehmensmodellObjekt <Kategorie>Quick-SortDomain <Netzwerk>Metropolitan area networkKomplex <Algebra>JSON
Transkript: English(automatisch erzeugt)
00:03
All right, good morning, everyone. Or no, yes, it's still morning. OK. My name is Jimmy. I wanted to talk a little bit today about crappy architecture and good architecture, especially in the face of all these microservices talks. I wanted to embrace the monolith and show an architecture that it could actually scale with
00:20
complexity. I have a feeling that a lot of the people that are doing microservices are doing this because they're already building on top of crappy platforms to begin with, like Ruby on Rails and Node. So with architectures that do actually scale, we can actually get a pretty long way to building out a nice system. So you can find me on Twitter at atjboegerd.
00:42
My GitHub is also the same, github.com slash jboegerd. Everything that we see today and all my open source projects are going to be on there. So there's a presentations repository. You can find everything here, as well as my other presentations. I blog on Los Techies, jimmyboegerd.lostechies.com. And that's about it.
01:01
So let's start the first thing. I want to talk about this crap. So most of what I do in real development is I work with existing legacy systems and try to build better systems from those ashes of those horrible Holber systems. And a lot of what I see of systems today are these big
01:21
balls of mud. And they came about not because any developer had an intention of creating this big pile of crap, but it just kind of happened over time. And a lot of what I see even on smaller systems is an assumption that some kind of layered architecture will somehow provide a better system in the end.
01:40
But what I found is that those layers don't actually produce anything better in the end. They just produce kind of a layered pile of crap and not actually anything good. So a few weeks ago, I had the privilege of reviewing some code from a customer of mine. This is a customer that their primary domain was manufacturing. And they weren't really coders.
02:01
They knew how to build things. They didn't know how to really write code. And so they would hire other people to come and write code for them. What this meant, though, is they didn't really have any expertise in understanding looking at code, whether it was good or bad. All they knew is that their deadlines were missed. There were a lot of bugs in the system. And they wanted to know why. So they asked me to come and take a look and say, it's not a very complex system.
02:20
Can you tell us why they're missing deadlines, why there are so many bugs? So looking at this application, it's clear to me that the inmates were definitely running the asylum. This is an app with a very small footprint. There were four screens, just four screens. And the purpose of this system was the company was moving off of a set of bespoke systems into one
02:41
centralized system for some of their manufacturing data. And the point of this system was really just to take the data out of the old systems and cleanse it, so to speak, and put it into their new system. And that was literally it, just screens to modify data, and then some basic workflow to say, I've approved this data, and now I can go on to the central system.
03:01
And that was it. Just a few screens, the home screen, and that was it. But somehow, with the system, there's just a few tables and workflow, the developers managed to create a code base with 20,000 lines of code. And on the one hand, I want to applaud them and say, well, that's actually pretty hard to do to create such an insanely complex system for something so simple.
03:23
So kudos to you for that. And as well as, let's see, I had two solutions and 12 projects to do this very, very simple thing. It was one of the most insane things I've ever seen. So I wanted to walk through real quick what their architecture currently was. So it first started out, this is a system built in the last six months, first started out with web forms,
03:44
which, red flag number one, if you're building a new system today and you're using web forms, you should probably just quit. Just stop doing anything development-wise, you shouldn't be using web forms at all. So from the ASPX page, there was the code behind. Now what I found was that with all those 20,000 lines of code, most of it actually resided in these two parts,
04:03
the ASPX page and the code behind. And the ASPX page would have, and literally there's like one, the other three pages were just like a login screen and a home screen, which you click the link to go to this screen. There's about 3,000 lines of code in the ASPX page, and then about another 3,000 or 4,000 lines of code in the
04:22
code behind. So with all those layers they had put in here, these 12 projects, they didn't actually solve their complexity problem. They just shoved it into exactly two files. So from there, the request hit a service proxy, which was another Visual Studio project to wrap WCF, because
04:40
what WCF needs is more abstraction. From there, it hit the actual WCF service proxy, and this is where we switched solutions. So now I was in the UI project. Now I have to go to the UI solution. Now I have to go to the service solution. I did ask the business, so why are you guys creating web services for this project?
05:01
Did they have to expose their capabilities to someone else? No, they didn't even know what a web service was. So again, just complete overengineering. So from here, we hit the actual WCF service. And from the WCF service, we hit the actual business application layer. I don't really know what the A actually stood for. In the name states, it said BAL, so I can just assume
05:21
it's business application layer. I don't know. Does anyone have any guesses for what the A stood for besides that? Abstraction, sure, why not? Because we need more of that. OK, so the business abstraction layer, architecture layer, application layer, whatever A stood for, it's lost to the ages because we deleted this repository soon after we met
05:43
these folks. From there, the business, the BAL, whatever that means, hit the DAL, which could only mean data access layer or data application layer or data abstraction layer. I don't know what. One of those three things. And looking at the code from each of these new layers, the service proxy, the BAL and the DAL, the entirety of those
06:05
codebases, just consisted of interfaces, then classes. And then inside those classes, each of the methods would be to do something like approve material data or something like that. And inside the method, there was one line of code that was actually real stuff. And that line of code would just call into
06:22
the next layer down. And then surrounding that was a try catch block, which caught whatever exception it might be, wrap it in a new exception based on that layer, and then throw that exception and maybe do some logging as well. Looking across the dozens of these service classes to see what, if there's any business logic in them, there was none.
06:40
It was literally try, catch, wrap, throw again. By the way, this whole talk is a complete straw man. I know you're probably not creating these architectures, right? OK. OK, so from the data access layer, which didn't do actually data access, it was still using ADO.net, raw, just like bare bones ADO.net. And from there, it actually called a stored procedure where
07:03
the actual data access logic lied. Funny enough, the stored procedure and everything going down were regular kind of DTOs. But on the way up, they decide to use data sets for everything, which I found just absolutely hilarious. So I actually built a tool called AutoMapper, which
07:22
enables some of these architectures. And so what I'm trying to do with this talk is to repent for some of the sins of people using my tool for very evil and nefarious purposes. When I first started out in .NET, this was when I first graduated university around the early 2000s, I was desperate
07:41
to see how people actually built systems. I went to school not for computer science or anything like that. I went to school for computer engineering. So I didn't really get any instruction from university about how to build actual applications. I knew about how to build things that made robots go, but nothing about actual systems. So I looked at the documentation and the guidance at the time to see how do people really build systems
08:01
these days. And naturally, my first result that came up was MSDN documentation for Microsoft about how to build systems with good architecture. Now, the picture they drew was something like this. I have a UI layer, a business logic layer, data access layer, and then finally the database.
08:21
Now, this should be warning number one. Don't take advice from people about architectures that don't actually build real systems. They just write about them. If they haven't actually built systems, then they're probably giving lousy advice. And this is what the advice that I was given early on was to build everything in layers. Now, that worked OK for small systems that I worked with, but my first real job and real product I had to
08:44
work with, I saw that this architecture wouldn't scale. So at the time, the Domain-Driven Design book just came out, and people were really latching onto this as a way to manage complex systems. So this was replaced by a much better architecture, the Domain-Driven Design style interior architecture, which is
09:03
obviously much better. Instead of my business logic layer, I have domain objects, and instead of a data access layer, I have repository. So it's actually pretty easy to move from an interior style architecture to Domain-Driven Design. You really just have to rename some files, and now you're doing Domain-Driven Design.
09:20
And when I built these systems, I would organize everything in my system by its layer. So if I had something that was person, and by the way, this is another huge warning. If you have something in your system called person, you've probably done something wrong in your modeling, as well as something called user. No one has a title of user or title of person in your company, so you should probably have a thing called person. And I would build things around this by its layer.
09:43
So I'd have a person class that had a person data in it. A person controller for MVC stuff. Then a person service, which is really where all the business logic lied, because I couldn't figure out how to put things in the actual domain layer. And then finally, a person repository, which again, was really just a gateway on top of the database.
10:02
So at the time, when it came time to actually building out my repository, it wound up looking something like this. Now this is actual code that I wrote, so I have no one to blame but myself on this one. I can't blame developers somewhere else. I built something like this for my repository. And it's actually cut off at the bottom because there's
10:20
actually too many methods for me to fit on the screen. So this repository had methods for basically every single kind of data access thing I would actually have in the system. And I might have something simple like get some person by its person ID, but if I needed to do extra data access or extra ORM fetching or something like that, I couldn't just use that.
10:40
I had to actually build new methods for every style of thing, every kind of thing I need to fetch out. So I started to have more methods for all the different ways that I actually query and on and on and on and on. So how can I dig myself out of this? The first thing I would need to do is to characterize what I'm actually doing in my application. And this is where I find that the architecture of the
11:01
web lends itself well to actually good back in architecture. The web itself is very, very simple. I have basically two kinds of requests in my system. I'm going to ignore the hipster ones, the put, patch, delete, those ones, and just focus on the ones that people actually use, get and post. And these two things are very, very simple. I use gets for reads, and I use posts for writes.
11:23
Gets are nice because they're safe in that they don't have side effects, and they're idempotent means I can call them over and over again, and the result is the same as if I called it once. The effect of emangling the definition, that's good enough. Post is for everything else. So if I'm mutating data, doing something where I'm modifying something in the server, I use posts, and then in that case, it's not guaranteed to be safe and not
11:43
guaranteed to be idempotent. Now it could be, but there's just no guarantees around that. So looking at these two things, I can see that my gets are queries, and my posts are commands. So queries to do something or to ask for some data, and then a command to then make some modification of the data
12:01
and go on with my day. Looking at my systems that I built, if I look in my application, the way I would organize and the way I built it out, I've got all these layers for every kind of thing in my system. I only have four layers here, but typically I see much more than that. I might have models, view models, services, all these
12:22
different folders for all these different layers. When it comes time to actually building something in the system, I don't build something across a layer. I'll build things down layers. So if I'm going to add a new screen to my system, I'm going to make a change to the user interface, create a new view file, create a new controller. I may have to make changes to some data objects.
12:44
I don't have repositories anymore, but we'll get to that. So what I'm doing here is making changes not across a layer, but down a layer. So every new feature I have in my system, something like view and approve invoices, I'm making changes to every single layer. Approving an invoice, again, every single layer, or
13:00
rejecting an invoice, I'm having to touch all these different places down the line. And typically, my solutions, I'm jumping all around to go ahead and make these changes. I often find that with these layered architectures, this is where a lot of the source of merge conflicts comes into play, because I'm modifying these objects that have changes across all these different layers.
13:21
For example, the repository here. For each of those three vertical yellow boxes, I'm making some change in that repository to add some functionality for whatever that is. So if more than one person is having to change these files across these layers, that's when I'm going to be running into conflicts. So about five years ago, I can't take credit for it, but
13:44
maybe I will anyway, there's a great pattern introduced that built on top of the idea of command query separation, and said, OK, how about instead of us just talking about methods where I have commands and queries, how about we talk about objects, where I have two different
14:02
models for commands and queries as opposed to just one. And really, this is coming around and looking at things like repositories and saying, well, we thought that repositories and that kind of pattern was there to help be a good example of something in Solid, that it
14:20
was a single responsibility because it did data access. The really big one, though, that I missed was the I in Solid, I being interface segregation principle, the idea that if I need to make a, I have no idea what ISP means, just know that repository violates it somehow. So the idea is that in my repository, if I look back at
14:42
the one that had all those different methods on it, for any given one of these vertical slices, I'm only using one of those methods in each different use case. You'd have just shoved them all in one file as if they have something to do with each other. But in reality, they don't. Recently, well, it's about a year ago, I guess, now, I was part of a project where we were moving ORMs.
15:02
I know that's something people never, ever do is like in a code base change ORMs, but it's something that we had to do. And for us, if I was using the abstraction in the repository, we would have never been able to make that switch because the repository was coupled to whatever ORM I was using underneath the covers. But with a more vertical slice architecture, we were
15:22
actually able to change our ORMs we were using. Originally, we were using Npoco, which is a micro-ORM, and Hibernate, which is a complete other side of the spectrum. And finally, we went to Entity Framework, which was kind of in the middle between those two choices. And we made these changes in the course of about two or
15:42
three days that we would never have been able to do with this style of architecture of layers. So it all came back for us to CQRS, where we built two models for once there was one. Now, if anyone was in Udi's talk yesterday on CQRS, this is the style of CQRS I'm talking about.
16:01
Not the asynchronous, invented, two databases, ridiculously architecture version, but where I'm just taking one object and splitting it into two, and that's it. So if I go back and look at my repository, in this repository, I have all these methods that are only used in one specific use case.
16:21
So there's really no reason for them to live together. If they don't change together, then they shouldn't live together. When I look at this, I also see a pattern emerging, in that I have a set of methods that describe some kind of distinct query for a specific use case. And then I may have things in there for specific commands, like remove updated flag from all people,
16:40
which is probably a bad idea to do anyway, but there it is. OK, so with this crazy layered architecture, I want to get to someplace better. The first thing I have to do is rewind all those abstractions. Unfortunately, when you have a very poorly architected system, it's difficult to refactor incrementally to something better. Sometimes you have to just take away all the crap and
17:02
put everything in one spot, and then you can go out and then put something else on top of it. So my first step was just to collapse all the layers and remove any kind of insanity on the system. So I looked at my system. This is MVC. And I know that gets are basically
17:20
queries and posts are commands. So if I start to look at those different controller actions and see what they're actually doing, I can start to see if there's any sort of concept that emerges from these two styles of usage, queries and commands. So removing all those repositories, doing the heinous thing of just doing data access directly in my controller, I know it's horrible, using it directly, I
17:41
start to look at, OK, what are these things doing? This is a more complex query in which I actually took this from Microsoft's new example on how to build systems, Contoso University, which is also another great example of how not to build systems. And I'm trying to take this and move it to some kind of better architecture.
18:01
So inside the controller action, I have a mix of concerns going on. I have some data access going on in which I'm going and querying entity framework for stuff. Then I'm putting stuff into view bag, which is just a data dictionary for storing stuff for views. And then finally, I'm telling MVC to go ahead and show a view by passing it a view model.
18:21
This is also another red flag I saw, mixing the data dictionary style of MVC and the strongly typed view side of MVC. If you're doing both of those things, it's really confusing. So this was a simple version. So this is a more complex query. I might have a more simple version that looks something like this, where it's literally one line of code.
18:41
There is one thing that is a little bit interesting here in that I have, in this case, I'm showing a list of departments, but I have this include method at the end. This include method says, I want you to go ahead and fetch that additional data. And there's one thing that developers do really well. It's managing ORM fetching.
19:01
Oh, no, I'm sorry. What we do horribly is manage ORM fetching. This is one of those things that I see as the shining example of why people don't like ORMs is we just have to do crap like this, fetching some additional data because I have to use it some time later. Now, in ORMs, it's actually relatively easy for us to go ahead and navigate associations on our data access
19:22
objects or entity models, whatever you want to call them, and then have the ORM go ahead and silently behind the scenes fetch stuff, which is actually a very horrible thing to do, to go and let the ORM just magically do queries behind the scenes. So instead of doing that, we have to kind of remember that, oh, by the way, we're using this other object over here, so go ahead and fetch that.
19:40
If we use anything else on those objects, we'll have to go back and change this to make sure we also include and fetch that additional data. Again, if there's something I have to remember to do, I'm going to forget, so I'd rather build on top of an architecture that forces me to be explicit about what I need. Some of my queries could be a little bit more complex in the
20:01
actual logic they're doing in the controller action. In this one, for example, whenever I'm modifying information, there's still a get that has to be made that shows the form, and then there's a post on the other side to actually modify the data. So in the get case, there's only a very small piece that's doing the actual data access. The rest of it is just other junk I'm using to take
20:22
care of other edge cases. So in this case, if they don't supply an ID, I need to return a bad request. Results, if the actual object isn't found, so they give me an ID, but the thing isn't in the database, then I return yet another thing. And then this is the kind of code that I see copy and
20:40
pasted all over the application. So this is another thing I'd like to address, is to figure out when I have these things that I see copied and pasted over and over again, what could I put in my system to address those cross-cutting concerns? And the answer is not another layer, by the way, but we'll get to what it is in the future. My commands look something like this.
21:01
I accept a form post, and the bound object is going to be my real entity in the database. And by the way, this is an absolute horrible thing to do in NBC, you never want to do this, where I have to whitelist a set of properties in my system. A few years ago, the reason why I have to whitelist is
21:23
because every single request variable is going to be attempted to be bound to my real data access or entity model to be persisted in the database. A few years ago, there was a security bug in Rails found in which Rails would take request variables and then bind them, state your active record objects, and then
21:42
persist them to the database. And any request variable that was found that happened to match something, whether it actually showed it on the form or not, would be bound up. So someone saw this bug and opened up a GitHub issue with the Ruby on Rails repository and said, hey, you have the security hole. You're just binding every single request variable.
22:02
Someone could use this to maliciously add extra request parameters to do whatever they wanted. Well, I guess the Ruby on Rails team wasn't quite as quick to fix this issue as the developer liked. And so the person that found the issue also knew that GitHub was built on Ruby on Rails. So they decided what they would do was exploit this
22:22
hole to make themselves an administrator of the project, open up a pull request to fix the issue, and then actually merge in the issue and close it. Now, the Rails team was not too happy about this. But it addressed the issue. So their solution for this was to use a form of
22:43
whitelisting or blacklisting to say, I only want these form fields to be bound up to my active record object, or in this case, my entity model and entity framework. Now, one thing I love about programming is strings, especially when I have strings that refer to properties that are really good for refactoring.
23:01
Because one thing that refactoring tools really do well is go through strings and figure out you're actually referring to a property. No, that's not true. That's one thing they do horribly. Again, this is one of those things that if I have to remember to have the exact right fields, then I'm going to forget. This is one of the things that could easily become divergent from what the actual truth is behind my form.
23:21
So if I make some change in my system where I'm now allowing them to edit some other piece of data on here, if I forget to include this here, that's going to be a very subtle bug that I only find when I actually run the application. No controller unit test is going to be able to find out that request variables aren't being bound correctly. If I just launched this controller up in a unit test and said, make sure that I can pass in a course, and
23:42
it'll get saved correctly, well, that's not actually exercising the real framework behind the scenes that will go through request variables and model binding and filtering here to make sure everything gets bound correctly. So I wouldn't actually know if something actually worked correctly until I ran the system.
24:00
A very bad thing to be. So what I'd like to do is build up concepts around both my queries and my commands in the system. So let's look first at how we might create queries. When I'm looking at my requests, just all the requests on my system that take in all my gets in my system, I
24:22
notice one pattern emerging that's almost a very functional style that my user interface layer has, which I have some kind of input in the form of query string parameters or form variables, and then something handles those and then produces some output. So it's always this input going in and endpoint going out.
24:41
What I'd like to do is represent those concepts, the inputs, the handling of the request, and the output as distinct things in my system. So I modeled this as some kind of query object that had the query input, something that handled the query, and then the query output. And I wanted to make those things completely divorced from anything in the user interface and just talked about
25:01
data coming in and data coming out. So we built something to help do this. I copied and pasted it several times and got to the point where this idea of request coming in and an object going out was simple enough that it created an open source project around it. So I built this open source tool called Mediator.
25:22
Of course, this was during the time when it was fashionable to not have vowels all the time and capitalize letters. So that's why it's called Mediator because it just seems like that's a thing people do now. So in this open source library, we're able to model commands and queries as requests and request handlers.
25:41
So going back to my controller action, what I first need to do to figure is figure out what my inputs are. So the inputs to whatever my handler is going to be, in this case, are going to be all of my query string parameters. All those pieces together, I can pull together and make it to a single request object that becomes my query input.
26:00
So all those query parameters become properties on a query object. And the outputs are going to be everything that's now fed to the view. So this includes not only the view model thing that was created, but also anything that was built into my view bag.
26:20
So taking everything that is an output to be able to be displayed or used somehow, I'm going to stick into a single object. So that means in my view, I need to go look to see what's actually being used there. I go to my view and see that I'm using these three properties to be able to be showing data on the screen.
26:40
So I'm going to build a response object that models all of that data that I'm going to be displaying on the screen, whatever form or fashion it might be used. So this result object is made distinct to that individual request. And what this is doing is ensuring that I'm not sharing any kind of logic or behavior across my individual
27:01
requests, that whatever happens, however crazy this page gets, its behavior and logic is confined to just these individual objects. This one, for example, shows a list of things. So I'm going to have a list of my student objects. In this case, I'm showing students on the screen. Now the naming of these objects, what I try to do is
27:22
name them after the request in my system. So in this case, this is the student controller and the index action. So just as a convention, I'm naming it student index model, and then every single request will have some sort of model associated with it. Now the final part, after I have my input and my output
27:41
defined, is actually handling that query request. And so this object is solely responsible for taking the input and building the output. And that's it. Now if I was in a more functional language, I would have to create a type around this. But because you're in C sharp, I had to create a class to hold this concept.
28:01
So this request handler takes the inputs of the query and returns a result. And whatever it has to do to build that up is inside the logic of that handler method. At this point, there's also no real reason for me to use any abstraction in all my data access. I can go ahead and just use any framework directly because this class is just responsible for taking the
28:21
input and building the output. Any kind of abstraction on top of this is just waste. One of the things I like to do with abstractions in my system is to wait as long as possible until I put them in there and make the code itself prove that it needs the abstraction before I put something in. So even today, I don't put in things like repositories or services or anything like that until the code tells me
28:43
that it actually is needed and not before. And so in this case, I didn't put a repository in because it didn't think I needed it and built the rest of the system without ever needing it. And until I need it, I'll put it in. But before then, I won't. The final piece here is building out the query result.
29:04
And this is where, for me, AutoMapper actually enters into the fold. I have a complex data object, or data entity model, whatever you want to call it, that has all these relationships and all these methods and all these ways to get around to all these different pieces. And what I'd like to do is have the same conceptual feel
29:23
of a database view where I have a description of the projection of what I want to have, but not to have to go through the work for every single request, creating an individual view. So what AutoMapper does for me here is it sees the input data model, the entity model, it sees the output model, and will automatically build for me a select
29:43
projection and link to build out the result that I want. So this project to page list replaces what I may have before as a link select projection that would get passed to the database as a select projection in SQL.
30:00
So what happens here behind the scenes is the link select projection is created, it gets passed into my ORM and entity framework, and entity framework takes that select projection, it builds out a special SQL query just for that individual page. Now what's interesting about this is it has only the
30:22
data that I need for that individual page. And on top of that, I don't have to do any sort of fetching or joining or anything like that. Because my select projection is navigating those relationships directly, the link provider knows how to take that select projection that navigates complex
30:41
relationships and turn it into one SQL query that does all the joining for me. So let's look at a more complex example, because that one has paging and stuff, but most of my pages don't have that sort of stuff. The minehead instead looks something like this. So this is the output of viewing details of an individual student at a university. It has regular data fields for the students.
31:04
And on top of that, it's going to tell me what courses that student is enrolled with. So if I was doing a regular link query, just through entity framework, and I was showing the real live students to be shown on the view, I would have to do things like fetching.
31:20
And that includes syntax to say, make sure you grab those enrollments. Because if you don't, as I look through the enrollments, it's going to query and go back to the database for every single one. What I'd like to do is go in one shot, get all the data back, and project it into this model. And now I have to go back to the database for every single thing. Nor do I want to have to instruct my ORM every single time I'm getting additional data to say, oh, and don't
31:42
forget to get that other thing. So in this one, for example, the enrollments not only has the grade information on here, but it links to a course, and that course has the title information. And so I'd have to have a very wonky syntax to get all these includes in place. But what I do instead is have one line here that says
32:00
project to singular default async. That's not the interesting part. But project that student into my student model, and just with one single SQL query, get all that data all at once. So this is the query result that comes back. I cut off the other junk that's the kind of crazy entity framework SQL stuff. It does some really weird things.
32:21
But the query plans are good, so who cares? And it pulls back just the fields that I have for that individual screen and nothing else. Now, each of those different objects, the enrollment and the course object, have a lot more data as part of it. But what I'm doing here is just pulling back only the pieces I actually need. This is why, for me, CQRS doesn't require a database view
32:42
for every single thing, because I'm using these projections to have the same exact result. So that's queries. Let's look at commands. Now, commands are interesting. Queries are pretty easy. They were very simple.
33:00
Everything's just input, output. Commands are where really I see a lot of business logic in place. So for these cases, I want to say, let's look at all the inputs for whatever I need to manipulate. And the outputs may or may not exist. Sometimes there is a result that comes out of executing
33:21
command. Sometimes there is not. Most often, there's not. So what are my inputs? Well, luckily, because I had to whitelist everything here, it's very easy to see what is only the data I need for this individual request. So in this case, it's that bind statement. Well, I still want to go back to the view to make sure I'm not missing anything. But this may be a good start.
33:42
So I take those three properties that I'm building out and say, let's create an object that represents that request to do some thing in my system. So in this case, the command is to create a student. So I'll have a class that represents this request called student create command. I guess I could have named it that was a little bit more
34:01
fluent in nature. But I wanted to keep with the same naming convention of controller, action, and then whatever. In my systems, I create more task-oriented user interfaces. The example I'm running from here, the Contoso University is very crud in nature. So there's not a lot of commands that represent real
34:22
business activities. But in the systems I work with, it's more like invoice approve command, invoice reject command. And I'll have individual forms and individual links on my screens to do those individual actions. Handling command is also now pretty straightforward. I take the input.
34:41
And in this case, because the object was really simple, I can now use automapper to go the other way and just manipulate my data entity model with whatever information is coming in. Now, if it gets more complex than this, if I'm doing some other kind of data access or some kind of entity model, I could be using, I don't know, I could be using event
35:02
sourcing, I could be using complex aggregate routes. I don't care what I'm using here at this point. Any kind of logic that is around this request is going to be limited to this one file. So if I need to refactor this, I need to break out something else, it's all in this one individual file. And again, no repositories because it's just not needed.
35:25
Now, there's some pieces missing here that I just skipped. In my controller earlier, we saw that there was basically one line of real business logic, which was creating a student and then saving it. But there's a lot of other junk going on with validation
35:40
and things not found and what to do in those cases. So I still have the issue of what I need to worry about of the cross-cutting concerns in my system. Now, these cross-cutting concerns are the kinds of code I see copied and pasted in method after method, but there's no real easy way for us to abstract those things out into some kind of service layer because, again, once I abstract this out into just
36:02
a lower-level abstraction, I still am not solving the actual problem of addressing these cross-cutting concerns. So I see this in my controllers a lot. If model state is valid, then do something. If model state is valid, then do something. If model state is valid, in this application, I saw this
36:21
model state is valid. I just looked at how many references there were to is valid, and there's something like 40 references to that property across my entire system, which means that if I ever need to extend the functionality around validation, I have to go to 40 different places. This is a relatively small system, and one recent application that we had to solve architecture, we had
36:40
something on the order of 120 controllers and about 500 actions, and there's no way I could change the cross-cutting concern of validation or any other thing like that if I have to go to hundreds of places to try to modify that. So what I want to do is shift how I think about validation, and currently this application is not validating
37:03
individual requests, but it's validating the entity as a whole. So make some changes on the data entity, and then validate that the entity's in a good state. But when I'm dealing with my business, I don't ever validate entities, I validate requests. So the other nice thing about having a model built
37:20
around queries and commands is that instead of my validation centering around entities after the fact, after they've been mutated into a bad state, I can instead say, let's wait and let's not make any changes to my model until I've actually validated the request. And so my validation now centers around requests, not entities.
37:43
In this case, I'm using a library called fluent validation who builds validation around objects. And so in this case, I build validators around every request in my system that needs some sort of validation. So I've got whatever validation is needed for creating a
38:00
student is right here. Now another nice thing about this is if validation is different for different kinds of requests, it's limited to these individual validation classes. So if creating a student has different validation than editing something, that's OK. I see this in systems a lot with legacy data that say, we're now requiring a field.
38:22
But I can't put that validation on the entity itself because there's consuming existing data that doesn't have that information in there. And I can't go backfill data. But in this case, because I have validation around individual requests, I can say, well, going forward, students that are created now have this validation. But for existing students, I could have different
38:41
validation for whatever those cases may be. I also moved away from attribute-based validation. Sometimes my validation becomes a little more complex, like make sure students have unique names or unique identification numbers. Because I'm centering my validation around classes, this type can actually take an entity framework db context
39:01
and then do something with it. So if I needed to query the database to do something, I could have it all in here as well. OK, so I've addressed the problem of validating entities. But I still have that is model state valid littered across my application. So I haven't actually solved that problem quite yet. To solve this one, we need to think a little bit
39:21
outside the box about how our validation flow typically works. So in our normal validation flow, we have some sort of post that makes a request to the server. The server says, is this valid or not? And then if it's not valid, it returns a 400 bad request HTTP status code, as well as the HTML page itself that
39:41
says, here's all new HTML. We'll follow all validations and errors inside of it. If it is valid, then I'll do work, persist my changes to the database, and then I'll do an issue of redirect to say, go ahead and redirect to this new page. Now this is assuming that I've also made the same choice of not building on top of single page application
40:02
frameworks and just done plain old post redirect get architecture. Now the downside to this approach is that if something goes wrong on the server, there's some sort of validation error, I have to have an entirely new HTML page to show the user that has to retain the state of whatever they put in there before. Including things like drop down lists and check boxes
40:21
and radio buttons and all sorts of other things that are actually kind of difficult to recreate if the only thing I'm working with is that very simple post data that came up. So what I'd like to do is modify my validation flow to optimize the success path, and if something goes wrong, then do something a little bit different.
40:40
So in my modified validation flow, I'm going to actually hijack whatever posts that happen in the server. So the user will click a button, their request gets hijacked with AJAX, I issue the post to the server, and then the server does the validation based on just the JSON data. If something goes wrong and the form is invalid, what I'll do instead of returning an entirely new set of
41:02
HTML, I'll instead only return an object that represents the validation errors. So here's the things that are invalid, and here are the property names for those things that can be bound up. So on my AJAX post, when I get the result back, I'll take that result and bind it directly to the screen because
41:23
I haven't actually refreshed the page and gotten any new HTML back. So I don't have to go wipe the entire screen, forget everything you've done, and then rebind everything with the validation errors. I simply take those validation errors and now just show them on the screen. The success case looks exactly the same. I'll just do work, make some change to this database, and
41:43
then do my normal redirect. So I'll do this now with a cross-cutting abstraction that actually exists in my MVC framework. So in this case, I'm using an action filter which gets executed in every single request, and if I find that my model state is not valid, then what I'll do is
42:02
I'll take, actually in this case, I'll just take the raw model state from the MVC framework, I'll serialize it to JSON, and return that as the content to the AJAX post on the browser side. The browser side will receive that, see that it's a 400 bad request, and then just bind that model state
42:23
directly to the form variables without having to refresh any of the other HTML on the screen. If things are successful, then it just goes on with the rest of the request. So my controllers no longer have if model state is valid anywhere. I've taken care of validation, that is, the validation flow
42:41
exactly once in a cross-cutting layer, and not have to worry about it really ever again. So the final piece of the puzzle is I've now built everything into vertical slices with requests modeled as queries and commands, but I've still got everything scattered across my entire solution organized by layer,
43:04
not by feature. So what I want to do is flip this on its head and not organize by layer, but organize by feature. If I look at when I need to make a change to my system, I've got all these different folders for all these different cross-cutting concerns, and when I need to do something like make a new page in my system, I'm going
43:22
to this folder, I'm going to this folder, I'm going to this folder, I'm going to this folder, probably three or four others to make this change in my system. And it becomes really difficult to follow what's going on when I'm having to go across my whole solution to make any sort of change. So what I want to do is collapse all these different
43:41
things together, all these different pieces that I need to create for an individual request on my system, and gather them in one place. And then what I'm going to do this is I'm going to collapse everything down into folders and namespaces based on those activities. So in this case, I'm creating a features namespace, and then another namespace based on whatever area, feature that
44:02
that request belongs to. In this case, I still just defaulted to the controller name, whatever that controller name is is going to be whatever my folder is for my features. And then inside of that, I'm going to create a class that represents whatever thing you're trying to do there. In this case, it's creating a student.
44:22
And then nested inside that class are all the different things that relate to that request. So in this case, I have the command object that represents whatever thing I'm trying to do. I have whatever validation that is whatever validation deals with that request. And then handling that request is now a handler inside
44:40
of this create class. Now I could have done this with a namespace, but I find that it's a little bit easier just to have everything in one file. That whenever I'm making a change to the system, everything I need to do is right here, and I don't have to go jump around anywhere. So now I can have entire features be collapsed into one file. So in this case, I have a query that shows the initial get
45:04
request for editing something. I have validation around my query. I have the command that represents the post. And then I have validation around the post. And then I have the handlers for both my query and my command. So I as a developer, when I'm creating a new feature in my system, everything's in one spot.
45:24
I'm going to go even one step further and have everything related to that feature, not just code, but also any kind of scripts or templates also be organized alongside them as well. So inside of this, I've got my edit C Sharp code.
45:41
I've got my edit view. And if there's any JavaScript associated with this, I have that alongside as well. So when I want to go see everything there is to do about a certain feature, I've got everything right together as close as possible. And you're starting to see this more and more on client-side frameworks as well. For example, React is embracing this concept as well. Instead of having things spread across services and
46:02
factories and factory service providers and blah, blah, blah, we have everything together that has to deal with one single component. And this is just another implementation of that sort of concept. So to achieve this, it's really just renaming the views folder to features and then making a little modification in
46:23
my view engine to be able to redirect those requests to go to the views folder to the features folder. So that's how I can then locate the views. And the final piece then is in my JavaScript side, I want to have individual modules for every single page.
46:41
I don't want to reuse my JavaScript across different screens. If there's something that needs to be reused, I'll extract that and put it to something that's not specific to any of my features. And so every single JavaScript file is now an individual required JS module per slice. And so this part right here is taking the, I want to have on
47:02
every single page that loads up one single block that says go ahead and fetch my required module for this individual page. And instead of me copying that pasting into multiple views, I'll have one spot that pulls out whatever controller I'm looking at, whatever action I'm on, and then goes into the features folder and loads up that
47:20
individual JavaScript file for that individual page, and then builds and names a required JS module around that. So if I wanted to go see all the JavaScript that is related to this one page, it's again in one single file directly alongside everything else that has to do with that one request.
47:41
So when I get to the end and look back at what my controller became, removing all these layers, it becomes now pretty simple. It's really just taking in requests and, via mediator, sending them out to the actual handlers that is that domain specific handling. But otherwise, this is just going to be UI logic.
48:01
And you could look at this and say, well, why do I need a controller in the first place? Well, I still need something to satisfy, in this case, routes in MVC. And I've also found that there are cases where I do have to deviate from this model. For example, if I'm doing something like file uploads, that's really crazy MVC logic that does require me to
48:20
get to the bare metal of MVC itself and the actual HTTP request. So in that case, that doesn't go through this normal mediator request. So I still need a controller to represent that handoff between the UI and my actual domain objects, or my actual domain layer. My end architecture now looks like this.
48:42
I've got a large number of features that are heavily isolated from each other, that I don't have any sort of shared responsibility between those. I have some things that are cross-cutting concerns, validation, authentication, authorization, logging, any sort of things like that. Those go across all the different features, but aren't
49:04
built on top of different layers. These are just built as cross-cutting concerns. And the final piece is then some sort of persistence that I typically have to share across my whole application anyway. But again, this is not domain-specific or not business-specific. It's just another tool I'm using across all layers of my system.
49:21
So one thing I found about this is these top pieces are all domain agnostic. These are pieces that I can take from project to project, and also very good cases for me to either build common shared libraries, open source projects, or components that I share across the different solutions I have in my company. These are the pieces that are actually
49:41
unique to my system. And I don't try to share across different pieces. So earlier when I said we were able to switch from Petapoco to Inhibernates to Entity Framework, this is really easier for us to do because I could take one individual feature slice at a time, migrate it, test it, make sure it works, and then move on to the next
50:01
feature without having a really long-length branch to the side that doesn't have to do this really big bang refactoring. I don't actually care the technology used in each of these feature slices either. They could be using stored procedures, if you're crazy. They could be using micro-RMs. They could be using anything. I don't even care. Because all of the logic and behavior associated with
50:22
each individual request is contained in that one set of request files, then those concerns don't leak across the different features in my system. I don't even worry about design patterns anymore. I don't worry about factories and strategies.
50:40
I mean, these feature folders, these feature slices, can be as procedural as you could possibly be, because who cares at this point? Just shove all the stuff in a file and just have it in one spot. If it does get crazy, I can always refactor in the future. But I'd actually prefer to have everything in one spot as opposed to trying to break it out into multiple abstractions.
51:02
So let's do a solid round-up to see how our architecture now deals and addresses solid. So looking at single responsibility principle, now that I have one class per feature concept, requests, results, commands, validators, I have now each class
51:21
responsible for one exact thing and only one thing. On open-close principle, I extend my application through cross-cutting concerns. In this case, it'll be things like the action filters, extra results. But I don't do those things in the actual meat of my application. I do that in horizontal slices that go across all the
51:42
different features in my system. The L just kind of goes away. I don't have inheritance anymore, so I don't have to worry about things getting substituted for other things. I don't have really wonky repository hierarchies or really crazy service hierarchies or base controller classes. All that kind of goes away if I just don't do inheritance and instead focus on individual classes for
52:03
individual responsibilities. And then this never comes up for me really ever again. And the interface segregation principle, because every single class is responsible for one individual thing and queries are separated from commands, I don't have to worry about having multiple people changing one
52:21
individual type to do multiple different things. Every single request has an individual class, and so it only has one reason to change. And finally, on the dependency inversion principle, or whatever it is, the thing that requires IOC containers, I save this for actual real external dependencies.
52:40
So real dependencies like I have to interact with some third-party web service or some third-party components, this is something for things I don't write in my system. So the only thing we saw so far in our system that I needed a true dependency for was that entity framework db context. I want to have something else manage the creation of that
53:00
and management of that and not have that inside my type. So what I see is that the number of dependencies in my system are greatly reduced because they don't have things like services and repositories to deal with anymore, it's just the actual meat of what's going on in my system and nothing else. I do have occasions where I have some things that are injected that I actually build, but these are things
53:20
that are wrapping those things I can't control. For example, I need to know who the current user is in the system. So instead of me doing something like going to the thread, current user, whatever, then I'll have something like an I user context that can be filled in and supplied by something else as well. So thank you, everyone, for attending my solid
53:41
architecture of slices, not layers, and I hope you join me in the anti-crap movement going forward. Thank you very much. We do have five minutes for questions. If anyone has anything, yes.
54:15
Yeah, so the question is, I showed a CRUD application, which I admitted I was doing a straw man at the
54:20
beginning, so this is being fully up front there. So what happens when things actually get complex? So I find in those cases, I let those handlers themselves tell me which direction to go. This becomes like true sort of TDD style, where as things get more complex in that handler, I can take that behavior and push it down into my domain objects. So my domain objects no longer become just sort of
54:40
dumb entity models that are just getting stuff in and out of the database, but I push that down. So if, for example, I was doing something like event sourcing, I would see that handler just now hand off that request straight to my aggregate route and had that do all the behavior, and then my handler itself is now just doing basically the handoff between data access and stuff and the actual domain.
55:03
So those domain objects don't live in the future folders because those are one of those cross-cutting things I have in my system. Any other questions? So this full example is of my GitHub, github.com slash J Boogard, and there's a
55:22
Contoso University where you can find an actual running example of everything you saw here. So thanks everyone, again.