Steve "ardalis" Smith's RU-vid channel. Find more from Steve at: ardalis.com twitch.tv/ardalis twitter.com/ardalis github.com/ardalis facebook.com/stevenandrewsmith
Another issue I have had with some dotnet tooling (but cannot remember which one, as it was a really long time ago), was a Norwegian character like æøå.
I found spaces in project paths to be an issue since I started using VB 6 30 years ago. I made a point to never use them from the root drive all the way down. The only place I use spaces is in the SLN name. Thanks for the video! I can image how hard this would be to troubleshoot.
Yeah, it's not like it took days but it took longer than I'd have liked. The compiler ought to detect if weird characters might be in the path and provide a better exception message, ideally.
Nice video, but we need more videos for things like how to handle a lot of languages for something like product, how to handle globalization and localization for also the products
Love this video, thanks! Question on the Result UpdateName(): Here I see that the Validation Result method is in the Entity. However if the field Name is used in several Entities, would it be practical to avoid copy-pasta by strongly typing the Name & put the validation logic there? Perhaps Name may not be the best example for my scenario, but Email could appear in multiple Entities?
Results are meant to avoid exceptions; guards throw exceptions. If you want to combine them, you'd need a try-catch in the method that returns Result<T> and in the catch block you'd return Result.Error and provide some details there.
Hello Steve. Nice video. Great to see you directly on my homepage :) I do have 2 short real life usage questions tho, related to a current microservice hierarchy I am developing. Could primary constructors and records be used also for commands, queries and integration bus events or would it be better to just use classes and have the whole record as a record and not one containing references? And secondly would be, why use MassTransit when you can very easily (and more performing) use MediatR for local orchestration?
Mostly brevity, which isn't a bad thing. They're just inconsistent with how everything, even other similar things, work which makes them confusing to many devs.
FastEndpoints not supporting ctor injection for DI is big sad. I am a huge fan of this pattern though, and it jives real well with vertical slice architecture which I've been pushing really hard to point of fewer physical layers overall and it has been going very well.
FastEndpoints uses constructor injection by default. I think maybe you mean Minimal APIs? fast-endpoints.com/docs/dependency-injection#constructor-injection
@@Ardalis I was following Nick's video for FastEndpoints and he called it out too. I requested the services in a constructor and got an error. I had to create public properties and use the [Inject] attribute on them iirc
@@Ardalis oh interesting. Went back and tried it again on a new project and it's fine. Fluke or added recently maybe? Oh well, I'm fully satisfied now :)
they are nice and i like to use them, but i dont understand why microsoft decided to make them mutable by default. i mean if the primary constructor parameters would always be readonly, i could still assign them to a mutable field or property when needed. instead of the other way around…
I agree that would have been an excellent decision, in hindsight. Probably they had reasons. I think they are looking into supporting readonly in C# 13.
You mean something like: public class A(Product product) { private Product _product = product; public void DoSomething => _product.UpdateState(); } Will this change the product that was passed into the constructor? It will behave exactly as if you had a (normal) constructor and you passed in Product there.
@Ardalis not exactly. If you call product.update (the one from primary). Did this mutation also impacted _product field ? As they share the same ref, I am wondering what happens. (Mutation can also happens outside the class 😱)
Hi Steve, Thank you for publishing the interesting video. However, the source code link is not working correctly. After clicking on the source code link, I was brought to a page saying, "Almost finished... We need to confirm your email address. To complete the subscription process, please click the link in the email we just sent you." Unfortunately, I haven't received any email (I've also checked the spam folder). Could you please look into this issue ? Thank you for your help
I do cover this in my course on Dometrain, actually. I show how different modules can use whatever architecture is most appropriate to that module, and specifically demonstrate Clean Architecture in one module and Vertical Slice Architecture in another.
Invaluable insights rooted in years of experience. That Ardalis - Help your future self by writing about how you solved something - Create a Personal Brand - Keep it simple, just write something valuable Best advice: When you spend time learning or fixing something more than a couple of minutes. Write a blog post about it.
Agree, wording is unnecessarily verbose. Does visual studio allow you to customize the toolbar? The default definitely encourages debug over run. What about hot keys? F5 is (marginally) quicker / simpler than ctrl + F5.
Yes, you can assign whatever keys you want, but in my experience it's a pain doing so because you have to redo it when you reinstall (yes, you can export/import) and it won't work whenever you work at someone else's machine (common if you pair or are a teacher/trainer)...
Hello, your template is awesome, I am always checking it and comparing it to others, I sure love the REPR pattern, the only headache I have is how to integrate authentication and authorization while respecting the intent of the architecture. A tutorial would be awesome
@@Ardalis I manage to make it work for my project using jwt auth/auth with fastendpoints and swagger, if you like I can send you the repo link, maybe it will help
The difference between a POCO and a DTO: a DTO has Dto in the class name or namespace. TL;DR - don’t do that. They are both just classes, records, or structs. All classes, records, and structs are used to transfer data, otherwise there wouldn’t be a point to them. I’ve never encounter a situation where it’s useful to make a distinction that a POCO is being used as a DTO, because that’s just naturally what they are for. It’s just not useful to talk about Dto’s. Maybe you have some classes that specifically represent the API between an app boundary like between modules, or returned from web requests, or from library calls; it’s better to name these classes based on your domain model - a class, record, strict, or namespace should never have Dto or DTO in the name.
Yeah like I said elsewhere, use the “Dto” suffix as a last resort. And no, not all classes are used to transfer data. Consider any service, and in particular stateless services.
They don't have behavior, so I consider them to be DTOs. You're correct, they're not POCOs, because they require a (marker) interface to function. My next video will probably be on the topic of interfaces and DTOs (and then I hope to move away from the whole DTO topic, but I might do ONE more after that).
Which kind of behavoir in Domain models do you talk about? Didn't get that one. All the examples of DDD's structure that I've seen uses anonymic domain-models.🤔
Like these, for very simple examples: github.com/ardalis/CleanArchitecture/blob/main/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/Project.cs github.com/ardalis/CleanArchitecture/blob/main/sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItem.cs Obviously in real world systems there would be even more special cases, invariants, events, etc.
I'm a little confused.. your DTOs have public setters but the POCO you demonstrated with an Update method isn't a DTO. So a DTO can only have a default setter, if you have logic in your setter (or getter) its not a DTO?
Correct. DTOs are just properties. I mentioned in another comment you could have static methods for mapping or validation or whatever and that doesn't break this rule because they're not on the instance.
Yeah, that's an overloaded term, which is why usually it's prefixed. There's Domain Model on one end and ViewModel on the other. I also see (and have used) ApiModel (since there are no views) as well, and a host of other variants. Domain Models should not be DTOs but ideally should be POCOs as much as possible. ViewModels in MVC should be DTOs, typically. But in MVVM frameworks ViewModels are where the logic lives, so it's not a 100% rule at all - depends on context. ApiModels I've only seen in MVC and are just used for transferring data over the wire, so should be DTOs.
Does a record really fit into the dto defintion? There's bunch of compiler generated code like PrintMembers, Deconstructer, a different constructor where you can also pass the record and it'll set the values from the object. it has it's own equality operators and stuff like that. Wouldn't a record in this case be more like a Poco since it has behaviour. If you're really nitpicky under the hood properties are compiled down to getter/setter which then change a private backing field~ Or, to come back to the main question, does compiler generated code not count and as long as you don't put custom code in it the record would still be considered as a dto?
You're not wrong, records *do* have a bunch of behavior, but it's mostly hidden away. They're very frequently used purely for data transfer purposes, though, which is why I tend to consider them DTOs by default, assuming no methods are added to them. Of course once you add your own custom/bespoke methods to your records, they cease to be DTOs.
@@Ardalis Okay, thanks for the answer. I'm using records too very often since they're very handy if your data-object is not too complex, and can be created very fast. One of the best features they've added into C#.
@@kurokyuu Take care, most of the built-in behaviors in records are not used but can increase the complexity of some scenarios, especially when using AOT.
I like the term Passive Data Object; PDO. If you're going to transfer something to somewhere else you might call it a DTO, but it is implicitly just a Serializable.
I've never seen that term before. You're welcome to try to make it a standard term, but regardless you'll run into folks already using the terms DTO and/or POCO in the dev community today. As I noted in other comments, it's useful for everyone to understand these terms so when folks use them, there's no confusion.
For better team communication mainly. If one team members suggests to another to use a DTO for something, that *means* something specific. Likewise if a complicated class is depending on some 3rd party library, suggesting replacing it with a POCO means something very clear as well... as long as you know what a POCO is. If you think POCO and DTO are the same thing, you're going to be be confused (or make the wrong change).
This would be a pretty dumb interview question to ask, IMO. It's useful mostly for clarity on teams. If one team members suggests to another to use a DTO for something, that *means* something specific. Likewise if a complicated class is depending on some 3rd party library, suggesting replacing it with a POCO means something very clear as well... as long as you know what a POCO is. If you think POCO and DTO are the same thing, you're going to be be confused (or make the wrong change).
@@ArdalisI don’t think DTO is a useful concept to talk about. It’s useful to talk about the design of the public APIs of your app; e.g where there are modular boundaries, network boundaries, public API boundaries, it’s useful to talk about what that interface looks like. But the language used should always use the terminology of the problem domain - e.g a web request returns a response; it doesn’t return a DTO. Based on the primacy of your chosen domain model nomenclature, DTO or Dto should never be added to a class name or namespace. Otherwise you’d get superfluous names like “Responses.UsersDto” or “UsersResponseDto” or “Dtos.Users”.
Where do you think validation should go? On the DTO? Or in the ingress function that accepts the DTO? For our internal Model classes, our team likes to do validation in the constructor; but I'm not so sure that is really necessary. Validation should perhaps only be for external interfaces?
Both ;-) DTO requires structural correctness (required fields have values of the required length, etc), POCO (or Behavior Rich Object) guards business invariants. As you told, this is usually a responsibility of constructor or method whcih mutate state. And ther's also a validation which covers a set of aggreates, this is effectively a buiness requirement.
Think. What is validation? Behavior. What does DTO's NOT have? ... Or to put it simply, it it has anything but data, it might be many things, a DTO is NOT one of them. Which is why, IMHO, thinking about DTO's without instantly thinking "Interfaces at transmission boundaries" is deleterious, because it muddies the waters if there is no a) interface or b) transmission. Structure it like this, for a "perfect world": - there's a plain data class PDC that presents the DTO in it's simplest form, ie, plain data - there's an interface PDI that presents the DTO in it's simplest form, ie, plain data - PDC, by force of definition implements PDI and should have a constructor that takes in PDI to create a PDC - there's a business class BC that has a .New(PDI) method that returns BC ( if all is well ), or even a Tuple<enum,BC> where enum represents either OK or some error - at the egress side of the transmission, you create a PDC, and ship it out as PDI Now at the ingress side of the transmission you have two options: - you receive PDI and turn it into PDC via PDC's PDI constructor, and pass it around as plain data, which has no guarantees of sanity, or - you receive PDI and turn it into BC via BC's .New(PDI) method. And HERE is where you perform validation. If all is well, the data was sane and you move forward, if not, you do whatever is appropriate While this might sound like a lot of work, it's a minute or two per DTO, and literally peanuts if you do it from the go. And you win on the fact that a) both sides MUST respect the interface and nothing but the interface and b) with strong "gatekeeping" of the interface, nobody will go around changing it willy nilly and breaking things. It might ruffle some feathers at the start, because some people are cowboys, but eventually they "fall in line".
I prefer separate classes for non-trivial validation, using a library like FluentValidation. This keeps DTOs from having this. I will say that it's generally fine for DTOs to have *static* methods on them for things like validation or mapping, for instance. The *objects* are DTOs and have no behavior; the static methods are just functions that happen to be attached to those class definitions. You generally do not want to have a bunch of guard clauses or constructor-enforced rules in your DTOs. They should just be getters and setters. No encapsulation. No checks. They can have attributes to assist with validation. Why? Because you don't want serialization/deserialization to fail midway or with an exception. You'd almost always rather have a nice list of validation errors than a serialization exception with a stack trace.
I don't usually use interfaces for DTOs - in fact that's a likely subject of an upcoming video. There are times when it's useful but most of the time the DTO *is* the contract and there's no need to add another type that mirrors it. I have seen some good uses cases for using interfaces with DTOs but usually they're not 1:1 but rather an interface that requires a certain subset of properties that many different DTOs might have which then allows for those DTOs to be treated a certain way collectively. But having one interface for ever one DTO class isn't something I've seen value in.
@@Ardalis I don't use it as a requirement but as a "nice to have". IMHO, the interface defines the DTO and the DTO implements the definition. By using an interface, which by definition is JUST a contract, both side are STRONGLY discouraged from relying on any internals of the DTO. Or even thinking about them ;) It does add a bit of work on the deserialization side, but i find that to be worth it in the long run.
It responds based on the vector database its model has created that guesses the most likely word to follow the stream of words it has output (in response to the stream of tokens/words that the user input).
Thanks for the video Ardalis :) I use F5 (Debug) just for the debugging, when I need to hit a breakpoint and check something at runtime. Ctrl+F5 (Run) when I want to check something on the UI or just checkt the process I made changes to :) the VS names are so so loooong, I'm a Rider user for last 6-7 years...