- **00:05**: Introduction, speaker's name is Scott Sauber, discussing tips for creating maintainable .NET apps. - **00:58**: Target audience: .NET developers interested in maintainable applications. - **01:55**: Goals: Expose new ideas and provide implementable tips. - **02:26**: Quote on complexity and simplicity in systems. - **03:20**: Speaker's background: Director of Engineering at Lean Techniques. - ****03:50****: Tip 1: **Folder Structure** - Use feature folders instead of default folders (e.g., controllers, models). - Promotes high cohesion by grouping related items together. - ****08:33****: Tip 2: **Treat Warnings as Errors** - Warnings should be treated as errors to enforce code quality. - ****09:58****: Tip 3: **Logging Best Practices** - Logs should be developer-focused. - Use structured logging. - Be cautious with the number of columns in log analytics. - **11:32**: Discusses differences between logs, metrics, and audits. - ****19:27****: Tip 4: **Use a Global Fallback Authorization Policy** - Set up a fallback policy for authorization in .NET applications. - ****20:54****: Tip 5: **Use Fluent Validation** - Separate validation logic from models using FluentValidation. - ****23:45****: Tip 6: **Remove Default Server Header in ASP NET Core** - Remove the server header to avoid exposing information to attackers. - ****25:23****: Tip 7: **Avoid Using IOptions Interface** - Register options classes directly to avoid unnecessary abstraction. - ****28:18****: Tip 8: **Version Endpoint** - Include a version endpoint in your application for better tracking and debugging. - ****31:13****: Tip 9: **Code Smells** - Keep the happy path at the bottom of methods. - Watch out for methods longer than 20 lines and classes longer than 200 lines. - **34:12**: Discusses security headers and best practices for Content Security Policy (CSP). - ****37:17****: Tip 10: **Build Once, Deploy Many Times** - Encourage continuous integration and avoid long-running branches. - **39:44**: Discusses the captive dependency problem in dependency injection. - **42:47**: Recommendation to use Fluent Assertions for better readability in tests. - **49:37**: Discusses the benefits of continuous deployment and the use of feature toggles. - **53:50**: Suggests keeping infrastructure as code in the same repository as the application code if specific to the application. - **54:57**: Q&A session begins.
Topic "enable" vs. "true": in regard to "nullable" you can enable/disable it with a precompiler directive in a file and you can "restore" it. So the value of "nullable" is more an enum than a bool. I prefer enums over bool because of clarity and readability.
There is tip to not put backend & ui in separaate repo becouse of 'deploy' - and that funny 'hit deploy button at same time' - which actually can be overcome with Contract Testing (for example using Pact) and nice tests which will make us more 'green confident' but also add 'Can I Deploy' this version to this environment - which is useful in case of microservices - which IMHO will be also a reason why you have two separated repos (actually more then that =] )
IaC and App in the same repo presents permissions problems. A dev may not be allowed to change infrastructure resources, such as firewall rules, replica counts, ingresses, etc.
@@josefromspace so who then is responsible? An OPS guy that knows nothing about the app in question and so is ill equipped to handle any issues that a developer might have known immediately?
If you’re in that spot where a dev can’t change IaC, you can setup a CODEOWNERS file (assuming GitHub) where a GitHub Team or user owns a folder path like /infrastructure and require an approval from that Team/User. Other platforms besides GitHub have similar features for this scenario. Ideal state would be if you require things to be done a certain way, you’re enforcing that with the pipeline (ie IaC scans) and stuff like cloud policies so that people have automated guard rails to fall into the pit of success and you’re not waiting on a manual review that causes delays and also might miss a misconfiguration if you’re relying on a person to spot it.
Ideally, neither a developer nor an ops person would be changing infrastructure. It would be changed by the deployment stage(s) of your pipeline. Then it's a question of who has the permission to trigger the pipeline stage (if it isn't fully automated). Even if you don't yet have a pipeline, building the application code and deploying the infrastructure changes will happen at different times, and may well be triggered or otherwise "done" by different people, but that's no reason why the code for both shouldn't live together. Keeping them together means the source of truth for each is in the same place, and they therefore can be reasoned about and changed with correspondingly easier and more direct reference to each other.
Treating warning as errors is a headache during the development phase, in this phase you don't need perfect code, you want to experiment and find answers as fast as possible, this involves having imperfect code that we are not ready to commit yet but that we might want to debug or test. So when do we need to have perfect code? easy, when we are ready to merge our changes. We can add a flag to the build step of our CI process `dotnet build -warnaserror`, that way we don't add speed bumps during the development phase, but we ensure we don't have dirty warnings in our code base.
I’m good with that too since you’re preventing it from getting it into main! I think the tradeoff you’re accepting there is people might have to re-test things after they make their changes and get “surprised” by a CI block they didn’t see locally. Not a problem if you have good automated tests you trust though! Appreciate you sharing the flag for dotnet build - I’ve never used that myself!
The problem with ignoring warnings because you think that dealing with them is some form of hinderance is that developers on your team won't then take the time to understand what the compiler is telling them, and then use that information to take an informed decision regarding what action to take. Eliminating them requires understanding, then you can choose to change the code if the warning is about a potential problem you hadn't considered,, or to explicitly ignore it (so the compiler doesn't show it) if you reason it to be irrelevant. Fix them early so they don't stack up and become unmanageable.
@@wyngates8408 Oh, no, I agree with you, I also think warnings should be addressed quickly. The developer should be responsible for not introducing new warnings, and treating warnings as errors is great for that purpose. I, as a developer, prefer the error to be thrown during the CI stage, instead of during development time. This is because during development time my code doesn't need to be perfect, I tend to iterate, finding a solution and refactoring until I have a good enough solution, during that period I can have a partial solution, it could be ugly code, it could have bugs, it could contain warnings, etc, and I don't want the compiler to get in the way of that process. But once I'm done with my code and I'm ready to merge I want the compiler to stop me if by mistake I'm introducing any warning. That's where `-warnaserror` shines.
@@mb7604-g9v Your way of working wouldn't work for me. If I write code the complier wants to issue a warning about, I want to address that immediately. That's because I want to understand what the compiler thinks might be an issue with what I've written, so I can assess whether that issue needs rectifying or not. I might still throw that code away at some point, but if I don't, then I don't have to return to it at some later time, when my focus on that particular piece of code has long been lost, to address the potential issues I left there. I accept that your way of working will address the issues at some point in time, but I want that point in time to be as close as possible to the time I'm thinking about the code, and that means as I write it.
Tbh many advices reminds me of bad advices on linkedin "do this instead of this". For example: classes with > 200 loc advice. Lol. Do not forget: there are many ways to do things correctly.
I think this most be highlighted more, because I heard so many people in work and on TH-cam claim, "switch to SeriLog", but I am still waiting for an compelling argument why an up-to date Nlog should be thrown out in favor of Serilog. It seams to be more of an preference choose rather than an technical choose. Both are good, use the one you like.
Most of those opinions would not hold water in any proper review of pros and cons. Like changing well-known project structure of MVC into something that is different for each project. Then naming your project with something as generic as "Core" and throwing all but UI dependencies into it. Fluent validation integrates with MVC pipeline via 3rd party Github project, no official support. Feature Toggles have tendency to leave you with bunch of unfinished/never finished code in the code base. Etc...
Well known project structure of MVC is wrong. The template is based on the temperature sample. Real projects should use domain context based naming. His advice on this point was spot on.
@@7th_CAV_Trooper Haha, `WeatherController` was added years later (if not a decade). MVC project structure was based on... MVC pattern. Problem with naming based on "domain context" is that you would not find a common naming ground even within a single team. Imagine when someone from outside world looks at your project. As your models become more complex and you need to divide them in multiple files and folders, mess would grow bigger. Further, Views (includes Areas) folders have to stay in their prescribed structure. So you can't copy a view into your "domain folder". I would also throw here that "complex validation" can be done by implementation `IValidatableObject` interface on a model. That interface exists since MVC v1.0, year 2009, and integrates natively in a binding pipeline. FluentValidation is not needed to achieve that. It's matter of taste, at the cost of performance and probably reliability. We can go on and on... I suspect that most of this advices come from a long time user of a different framework, and lack of knowledge of existing MVC features and concepts.
The advice in this presentation is almost all good and beneficial. Vertical slicing/organizing by feature promotes loose coupling and high cohesion,, both highly desirable. Tight coupling is the cause of most regression type bugs, when changes to the behaviour in one part of an application result in unintended changes to a different part, because those parts are coupled together in some way by some common feature. Vertical slices are much better at promoting the separation of different parts of an application when compared with the horizontal slicing or layering promoted by the default structure. Also, the default structure promotes and emphasises the framework, not the business solution. Most developers working with this technology are familiar with the framework, and don't need it to be promoted by the directory structure. By definition, the framework is the same for every application using this technology, and doesn't need emphasising. Conversely, the one thing that's unique about any given application is the business features it supports and implements. All developers working with the application need to understand those business features, so emphasising them via the application's structure is massively advantageous.
@@wyngates8408 ASP.NET MVC is highly testable and decoupled, because it follows MVC pattern and separation of concerns well. Loose coupling has nothing to do with vertical slicing of folders. Regarding high cohesion, it is not only about folder structure. In this case, it is affected more by how you group your functionality (actions) per controllers. Folders for Views and Areas have to follow specific structure so that display, edit, shared views and layouts can be found by engine. Following "feature folders" you introduce 2nd, parallel convention. And what happens when you have to renderer ViewModel, from, let's say "User" feature in header, or elsewhere? In the same way you can find use-cases that break cohesion of default MVC folder structure, you can find use-cases that break "folder per feature" structure. Over last 2 decades it has been proven that, when smarter developers start to build smarter conventions, smarter helper functions wrappers, smarter model binding on top established frameworks, it leads to maintenance problems and hard to reproduce bugs. Because it always turns out that they haven't been familiar enough with important use-cases that underlying framework supports and deeper reasons for certain framework behaviours. And lastly, when you hire externals or new developers, do you want to spend time explaining that your version of ASP.NET MVC behaves differently? I am sure that, if you would follow standard MVC structure to build your business features, no developer would have problem to find his way around. They would be able to start contributing sooner and you would avoid lot of confusion.
- **00:05**: Introduction, speaker's name is Scott Sauber, discussing tips for creating maintainable .NET apps.
- **00:58**: Target audience: .NET developers interested in maintainable applications.
- **01:55**: Goals: Expose new ideas and provide implementable tips.
- **02:26**: Quote on complexity and simplicity in systems.
- **03:20**: Speaker's background: Director of Engineering at Lean Techniques.
- ****03:50****: Tip 1: **Folder Structure**
- Use feature folders instead of default folders (e.g., controllers, models).
- Promotes high cohesion by grouping related items together.
- ****08:33****: Tip 2: **Treat Warnings as Errors**
- Warnings should be treated as errors to enforce code quality.
- ****09:58****: Tip 3: **Logging Best Practices**
- Logs should be developer-focused.
- Use structured logging.
- Be cautious with the number of columns in log analytics.
- **11:32**: Discusses differences between logs, metrics, and audits.
- ****19:27****: Tip 4: **Use a Global Fallback Authorization Policy**
- Set up a fallback policy for authorization in .NET applications.
- ****20:54****: Tip 5: **Use Fluent Validation**
- Separate validation logic from models using FluentValidation.
- ****23:45****: Tip 6: **Remove Default Server Header in ASP NET Core**
- Remove the server header to avoid exposing information to attackers.
- ****25:23****: Tip 7: **Avoid Using IOptions Interface**
- Register options classes directly to avoid unnecessary abstraction.
- ****28:18****: Tip 8: **Version Endpoint**
- Include a version endpoint in your application for better tracking and debugging.
- ****31:13****: Tip 9: **Code Smells**
- Keep the happy path at the bottom of methods.
- Watch out for methods longer than 20 lines and classes longer than 200 lines.
- **34:12**: Discusses security headers and best practices for Content Security Policy (CSP).
- ****37:17****: Tip 10: **Build Once, Deploy Many Times**
- Encourage continuous integration and avoid long-running branches.
- **39:44**: Discusses the captive dependency problem in dependency injection.
- **42:47**: Recommendation to use Fluent Assertions for better readability in tests.
- **49:37**: Discusses the benefits of continuous deployment and the use of feature toggles.
- **53:50**: Suggests keeping infrastructure as code in the same repository as the application code if specific to the application.
- **54:57**: Q&A session begins.
Topic "enable" vs. "true": in regard to "nullable" you can enable/disable it with a precompiler directive in a file and you can "restore" it. So the value of "nullable" is more an enum than a bool. I prefer enums over bool because of clarity and readability.
Its a good opinions topic. I don't agree with some, but it's brought clean and understandable.
There is tip to not put backend & ui in separaate repo becouse of 'deploy' - and that funny 'hit deploy button at same time' - which actually can be overcome with Contract Testing (for example using Pact) and nice tests which will make us more 'green confident' but also add 'Can I Deploy' this version to this environment - which is useful in case of microservices - which IMHO will be also a reason why you have two separated repos (actually more then that =] )
Excellent talk
This talks is excellent and it has been featured in the last issue of Tech Talks Weekly newsletter 🎉
Congrats Scott!
Indentation Proclamation is just Never Nester but more polite
Why is serilog recommended over NLog? what is the upside of serilog?
Actually good talk 🙂
IaC and App in the same repo presents permissions problems. A dev may not be allowed to change infrastructure resources, such as firewall rules, replica counts, ingresses, etc.
Then you have an organizational problem with devops if that is the case
@@Mig440 Not really.
@@josefromspace so who then is responsible? An OPS guy that knows nothing about the app in question and so is ill equipped to handle any issues that a developer might have known immediately?
If you’re in that spot where a dev can’t change IaC, you can setup a CODEOWNERS file (assuming GitHub) where a GitHub Team or user owns a folder path like /infrastructure and require an approval from that Team/User. Other platforms besides GitHub have similar features for this scenario.
Ideal state would be if you require things to be done a certain way, you’re enforcing that with the pipeline (ie IaC scans) and stuff like cloud policies so that people have automated guard rails to fall into the pit of success and you’re not waiting on a manual review that causes delays and also might miss a misconfiguration if you’re relying on a person to spot it.
Ideally, neither a developer nor an ops person would be changing infrastructure.
It would be changed by the deployment stage(s) of your pipeline.
Then it's a question of who has the permission to trigger the pipeline stage (if it isn't fully automated).
Even if you don't yet have a pipeline, building the application code and deploying the infrastructure changes will happen at different times, and may well be triggered or otherwise "done" by different people, but that's no reason why the code for both shouldn't live together. Keeping them together means the source of truth for each is in the same place, and they therefore can be reasoned about and changed with correspondingly easier and more direct reference to each other.
Treating warning as errors is a headache during the development phase, in this phase you don't need perfect code, you want to experiment and find answers as fast as possible, this involves having imperfect code that we are not ready to commit yet but that we might want to debug or test.
So when do we need to have perfect code? easy, when we are ready to merge our changes. We can add a flag to the build step of our CI process `dotnet build -warnaserror`, that way we don't add speed bumps during the development phase, but we ensure we don't have dirty warnings in our code base.
I’m good with that too since you’re preventing it from getting it into main! I think the tradeoff you’re accepting there is people might have to re-test things after they make their changes and get “surprised” by a CI block they didn’t see locally.
Not a problem if you have good automated tests you trust though!
Appreciate you sharing the flag for dotnet build - I’ve never used that myself!
@@scottsauber Yeah I guess there's always a tradeoff, by the way, I missed mentioning that this was a really nice talk, very insightful
The problem with ignoring warnings because you think that dealing with them is some form of hinderance is that developers on your team won't then take the time to understand what the compiler is telling them, and then use that information to take an informed decision regarding what action to take. Eliminating them requires understanding, then you can choose to change the code if the warning is about a potential problem you hadn't considered,, or to explicitly ignore it (so the compiler doesn't show it) if you reason it to be irrelevant.
Fix them early so they don't stack up and become unmanageable.
@@wyngates8408 Oh, no, I agree with you, I also think warnings should be addressed quickly.
The developer should be responsible for not introducing new warnings, and treating warnings as errors is great for that purpose. I, as a developer, prefer the error to be thrown during the CI stage, instead of during development time.
This is because during development time my code doesn't need to be perfect, I tend to iterate, finding a solution and refactoring until I have a good enough solution, during that period I can have a partial solution, it could be ugly code, it could have bugs, it could contain warnings, etc, and I don't want the compiler to get in the way of that process.
But once I'm done with my code and I'm ready to merge I want the compiler to stop me if by mistake I'm introducing any warning.
That's where `-warnaserror` shines.
@@mb7604-g9v Your way of working wouldn't work for me.
If I write code the complier wants to issue a warning about, I want to address that immediately.
That's because I want to understand what the compiler thinks might be an issue with what I've written, so I can assess whether that issue needs rectifying or not.
I might still throw that code away at some point, but if I don't, then I don't have to return to it at some later time, when my focus on that particular piece of code has long been lost, to address the potential issues I left there.
I accept that your way of working will address the issues at some point in time, but I want that point in time to be as close as possible to the time I'm thinking about the code, and that means as I write it.
10 opinions on .Net plus bunus opinion on name of X
at 33:27 about how more indentation makes code harder to read, I was thinking about yaml :-P
Early returns and 20 line functions. Sounds great😂
So, you're saying we should use Serilog because of maintainability? Are you seriously claiming that using NLog will make .NET apps unmaintainable?
Tbh many advices reminds me of bad advices on linkedin "do this instead of this". For example: classes with > 200 loc advice. Lol.
Do not forget: there are many ways to do things correctly.
it's true and all the software engineers in the audience switched their codebase to Serilog when they came back from the conference. 😂
He didn't say that at all. He said he preferred Serilog and disclaimed the entire talk with "these are just opinions"
Nlog 5 is just as good as Serilog now. Nlog gets a bad rep because
I think this most be highlighted more, because I heard so many people in work and on TH-cam claim, "switch to SeriLog", but I am still waiting for an compelling argument why an up-to date Nlog should be thrown out in favor of Serilog. It seams to be more of an preference choose rather than an technical choose. Both are good, use the one you like.
Most of those opinions would not hold water in any proper review of pros and cons. Like changing well-known project structure of MVC into something that is different for each project. Then naming your project with something as generic as "Core" and throwing all but UI dependencies into it. Fluent validation integrates with MVC pipeline via 3rd party Github project, no official support. Feature Toggles have tendency to leave you with bunch of unfinished/never finished code in the code base. Etc...
Well known project structure of MVC is wrong. The template is based on the temperature sample. Real projects should use domain context based naming. His advice on this point was spot on.
@@7th_CAV_Trooper Haha, `WeatherController` was added years later (if not a decade). MVC project structure was based on... MVC pattern. Problem with naming based on "domain context" is that you would not find a common naming ground even within a single team. Imagine when someone from outside world looks at your project. As your models become more complex and you need to divide them in multiple files and folders, mess would grow bigger. Further, Views (includes Areas) folders have to stay in their prescribed structure. So you can't copy a view into your "domain folder".
I would also throw here that "complex validation" can be done by implementation `IValidatableObject` interface on a model. That interface exists since MVC v1.0, year 2009, and integrates natively in a binding pipeline. FluentValidation is not needed to achieve that. It's matter of taste, at the cost of performance and probably reliability. We can go on and on...
I suspect that most of this advices come from a long time user of a different framework, and lack of knowledge of existing MVC features and concepts.
@@nenadvicentic naming is hard. As we said in the army, embrace the suck.
The advice in this presentation is almost all good and beneficial.
Vertical slicing/organizing by feature promotes loose coupling and high cohesion,, both highly desirable. Tight coupling is the cause of most regression type bugs, when changes to the behaviour in one part of an application result in unintended changes to a different part, because those parts are coupled together in some way by some common feature. Vertical slices are much better at promoting the separation of different parts of an application when compared with the horizontal slicing or layering promoted by the default structure.
Also, the default structure promotes and emphasises the framework, not the business solution. Most developers working with this technology are familiar with the framework, and don't need it to be promoted by the directory structure. By definition, the framework is the same for every application using this technology, and doesn't need emphasising.
Conversely, the one thing that's unique about any given application is the business features it supports and implements. All developers working with the application need to understand those business features, so emphasising them via the application's structure is massively advantageous.
@@wyngates8408 ASP.NET MVC is highly testable and decoupled, because it follows MVC pattern and separation of concerns well. Loose coupling has nothing to do with vertical slicing of folders.
Regarding high cohesion, it is not only about folder structure. In this case, it is affected more by how you group your functionality (actions) per controllers. Folders for Views and Areas have to follow specific structure so that display, edit, shared views and layouts can be found by engine. Following "feature folders" you introduce 2nd, parallel convention. And what happens when you have to renderer ViewModel, from, let's say "User" feature in header, or elsewhere? In the same way you can find use-cases that break cohesion of default MVC folder structure, you can find use-cases that break "folder per feature" structure.
Over last 2 decades it has been proven that, when smarter developers start to build smarter conventions, smarter helper functions wrappers, smarter model binding on top established frameworks, it leads to maintenance problems and hard to reproduce bugs. Because it always turns out that they haven't been familiar enough with important use-cases that underlying framework supports and deeper reasons for certain framework behaviours.
And lastly, when you hire externals or new developers, do you want to spend time explaining that your version of ASP.NET MVC behaves differently? I am sure that, if you would follow standard MVC structure to build your business features, no developer would have problem to find his way around. They would be able to start contributing sooner and you would avoid lot of confusion.