Milan Jovanović

17.8K posts

Milan Jovanović banner
Milan Jovanović

Milan Jovanović

@mjovanovictech

I make .NET content

Katılım Haziran 2022
777 Takip Edilen48.7K Takipçiler
Sabitlenmiş Tweet
Milan Jovanović
Milan Jovanović@mjovanovictech·
Stop wasting the first 3 days of every project setting up boilerplate. I built a free, production-ready Clean Architecture template to handle the setup for you. It's already used by 40,000+ developers. Here is what's inside the latest version: - 𝗨𝗽𝗱𝗮𝘁𝗲𝗱 𝘁𝗼 .𝗡𝗘𝗧 𝟭𝟬 - 𝗡𝗲𝘄 .𝘀𝗹𝗻𝘅 𝗦𝗼𝗹𝘂𝘁𝗶𝗼𝗻 𝗙𝗼𝗿𝗺𝗮𝘁 - CQRS (no MediatR dependency) - Structured Logging & Validation - JWT Authentication & RBAC - Docker Compose support It saves you hours of setup time. Download it for free here: milanjovanovic.tech/templates/clea… 💡 Why the shift to .slnx? The new Solution format in .NET is a game-changer. It replaces the old bloated syntax with a clean XML syntax. It makes merge conflicts in your solution file a thing of the past.
Milan Jovanović tweet media
English
4
16
108
11.5K
Milan Jovanović
Milan Jovanović@mjovanovictech·
Most systems don’t need global message ordering. They need something more specific: Events must be handled in order per aggregate. Per `OrderId`. Per `InvoiceId`. Per `CustomerId`. That distinction matters. Because the naive solution is to force everything through one ordered stream. And yes, that restores ordering. But it also gives you: - one worker - one throughput ceiling - one scaling bottleneck - latency spikes under load So teams usually reach for competing consumers. Now the system scales. But two messages for the same aggregate can be processed at the same time. `PaymentCaptured` can run before `OrderPlaced`. Retries and redelivery can make this even messier. That’s the real problem: Queues scale work. They don’t preserve your business invariants. If you follow the problem from first principles, the shape becomes clearer: 1. Aggregates define where ordering matters 2. The Outbox makes publishing reliable 3. Competing consumers can break per-aggregate order 4. A single consumer restores order but kills scale 5. Publishing the next message creates sequential progress per aggregate And once you do that, you’ve stopped building “event handlers.” You’ve started building a workflow. That workflow has a name: A saga. Choreographed first. State machine when you need visibility, timeouts, retries, and compensation. Broker features like SQS FIFO message groups, Azure Service Bus sessions, and Kafka partitions can help with per-key ordering. But they don’t remove the need to model the workflow when the business process itself can partially fail. Ordered handling per aggregate at scale is not just a queue feature. It’s a workflow design problem. I wrote a full breakdown of how to think about message ordering from first principles: milanjovanovic.tech/blog/solving-m…
Milan Jovanović tweet media
English
3
11
68
5K
Milan Jovanović
Milan Jovanović@mjovanovictech·
There is a specific kind of restlessness that only developers understand. An idea keeps running in the background. While you’re eating. While you’re walking. While you’re trying to sleep. You don’t know if it will become a business. You don’t know if anyone will use it. You just feel the urge to build it. I chased that feeling hard a few years ago. I wanted to build a small SaaS, ship it, get users, maybe turn it into a real business. Most of those ideas didn’t work. But looking back, I don’t see wasted time. I see the place where my skills were forged. When you build something from scratch, you learn differently. You own every decision: - database schema - authentication - background jobs - payment integrations - deployment - UI details - ugly tradeoffs No tutorial gives you that. No course fully replaces it. You develop judgment by making decisions and living with the consequences. That urge eventually pushed me in a direction I didn’t expect: content creation. I stopped building SaaS products and started building educational content for developers. Different kind of building. Same itch. And honestly, it became the most rewarding work I’ve done. But the urge to build something new never really goes away. Maybe that’s the point. Maybe the outcome is not always the product. Maybe the transformation is. I wrote a more personal post about side projects, failed SaaS ideas, content creation, and why I think every developer should build something of their own: milanjovanovic.tech/blog/the-urge-…
Milan Jovanović tweet media
English
4
14
44
2.3K
Anton Martyniuk
Anton Martyniuk@AntonMartyniuk·
I use Rider daily, and the code coverage tool is really helpful. And I also really like Junie in Rider, as it offers the same great experience as Cursor. So they are adding more AI integrations to their code coverage tool? That's super great, it will help me write more tests, without back-and-forth prompts.
English
1
0
1
216
Milan Jovanović
Milan Jovanović@mjovanovictech·
AI-generated tests are only as good as the context you give the model. And this is where I think IDEs have a big advantage. I use JetBrains tools daily. ReSharper is always part of my .NET workflow, and one of the reasons I like these tools is that they already understand a lot about my codebase before AI enters the picture. That’s why I found this new Rider EAP feature interesting. In Rider 2026.2 EAP, JetBrains is introducing a bundled AI agent skill called finding-tests. @jetbrains says this approach can reduce token consumption by up to 50% in their internal benchmarks, mostly using Claude. That means lower AI costs, or more useful work from the AI quota you already have. The idea is simple: instead of asking an AI agent to blindly search your codebase and guess where a test should go, Rider runs dotCover coverage analysis to map the existing tests around the C# code you’re working on. It can then point the agent to a relevant test file, where it can read nearby tests and follow the project’s existing style. That matters because most real-world .NET projects already have a testing style. Maybe you use xUnit fixtures. Maybe you have custom builders. Maybe your tests follow a very specific naming convention. Maybe there’s already a test class nearby that the AI should extend instead of inventing something new. The more the AI has to discover this on its own, the more tokens you burn. And the more likely it is to produce something that technically compiles, but doesn’t really fit your project. The trade-off is that Rider has to run the tests needed for dotCover to build this map. If you enable it for an entire solution, that means running the existing tests across that solution. On a small project, that might be around 30 seconds. On a large enterprise solution, it can be much longer. So this won’t be the right default for everyone. You can disable the skill globally or per project. Also, obvious but important: AI-generated tests still need review. Tests can introduce bad assumptions, weak assertions, or weird side effects. Don’t commit them just because they were generated. The EAP is free to try, even without the dotUltimate license normally required for dotCover inside Rider. Learn more here: blog.jetbrains.com/dotnet/2026/05… Worth testing if you care about better AI-generated tests, lower AI costs, and IDE-native context.
Milan Jovanović tweet media
English
3
12
53
3K
Milan Jovanović
Milan Jovanović@mjovanovictech·
@TheCodeMan__ I think we're all going to start appreciating features like this a lot more with the cost of tokens continuing to rise (re: Copilot price change taking effect soon)
English
0
0
2
181
Stefan Đokić | .NET
Stefan Đokić | .NET@TheCodeMan__·
@mjovanovictech This is the kind of AI integration that actually makes sense to me, using IDE knowledge and existing project conventions instead of treating the codebase like raw text every single time.
English
1
0
1
252
Anton Martyniuk
Anton Martyniuk@AntonMartyniuk·
I replaced 20 Python scripts with C# this year. Here is the workflow. For years, every time I needed a 20-line script, I created a solution, added a csproj, and waited for scaffolding to finish. That's why I switched to writing them with Python. .NET 10 changed that and .NET 11 Preview 3 finished the job. Now I run a single C# file directly: 𝚍𝚘𝚝𝚗𝚎𝚝 𝚛𝚞𝚗 𝚑𝚎𝚕𝚕𝚘.𝚌𝚜 No solution. No project. Just code. 𝗧𝗵𝗲 𝟰 𝗱𝗶𝗿𝗲𝗰𝘁𝗶𝘃𝗲𝘀 𝘁𝗵𝗮𝘁 𝗰𝗵𝗮𝗻𝗴𝗲 𝗲𝘃𝗲𝗿𝘆𝘁𝗵𝗶𝗻𝗴 These turn a single file into a real .NET application: 𝟭. #:𝗽𝗮𝗰𝗸𝗮𝗴𝗲 ↳ Reference any NuGet package with one line ↳ #:package Newtonsoft.Json@13.0.3 𝟮. #:𝘀𝗱𝗸 ↳ Switch SDKs to unlock ASP .NET Core ↳ #:sdk Microsoft[.]NET[.]Sdk[.]Web 𝟯. #:𝗽𝗿𝗼𝗷𝗲𝗰𝘁 ↳ Reference an existing class library ↳ #:project ../MyLibrary/MyLibrary.csproj 𝟰. #:𝗶𝗻𝗰𝗹𝘂𝗱𝗲 (new in .NET 11 Preview 3) ↳ Split your code across multiple files ↳ #:include models.cs That last one is the breakthrough. In .NET 10, file-based apps were stuck in one file. Now you structure a real app: models in one file, services in another, entry point in main.cs. 𝟱 𝗣𝘆𝘁𝗵𝗼𝗻 𝘀𝗰𝗿𝗶𝗽𝘁𝘀 𝗜 𝗿𝗲𝗽𝗹𝗮𝗰𝗲𝗱 𝗳𝗶𝗿𝘀𝘁 → JSON and CSV transformations → Internal API smoke tests after deployment → Log file parsing to extract error patterns → Database seeding for local dev → HTTP health checkers for staging environments The win is huge. I get strong typing, IntelliSense, and async/await out of the box. I share code with my main .NET projects without rewriting a single line. And when a script grows up, one command converts it: 𝚍𝚘𝚝𝚗𝚎𝚝 𝚙𝚛𝚘𝚓𝚎𝚌𝚝 𝚌𝚘𝚗𝚟𝚎𝚛𝚝 𝚖𝚊𝚒𝚗.𝚌𝚜 Tomorrow, I'm sending my complete file-based apps guide to 25,000+ .NET developers: ✅ All 4 directives with real, runnable examples ✅ Building an HTTP health-check tool across 2 files ✅ A working Minimal API with EF Core and SQLite in just 3 files ✅ The exact rules for when to convert to a full project This is the workflow that finally made C# competitive with Python and JavaScript for scripting. 📌 Subscribe to my weekly .NET newsletter, so you don't miss the issue: ↳ antondevtips.com/?utm_source=tw… What language do you write scripts in? —— ♻️ Repost to help other .NET developers build apps with 1 C# file ➕ Follow me ( @AntonMartyniuk ) to improve your .NET and Architecture Skills
Anton Martyniuk tweet media
English
4
9
46
3K
Anton Martyniuk
Anton Martyniuk@AntonMartyniuk·
𝟲 .𝗡𝗘𝗧 𝗧𝗿𝗲𝗻𝗱𝘀 𝗧𝗵𝗮𝘁 𝗔𝗿𝗲 𝗞𝗶𝗹𝗹𝗶𝗻𝗴 𝗬𝗼𝘂𝗿 𝗣𝗿𝗼𝗷𝗲𝗰𝘁𝘀 Every .NET tutorial sells these as "best practice." After 12+ years of building real systems, I dropped all 6. Here's what I use instead 👉 𝟭. 𝗖𝗹𝗲𝗮𝗻 𝗔𝗿𝗰𝗵𝗶𝘁𝗲𝗰𝘁𝘂𝗿𝗲 𝗘𝘃𝗲𝗿𝘆𝘄𝗵𝗲𝗿𝗲 ❌ 4 projects and 5 layers to navigate just to add one endpoint. ✅ Use Vertical Slice Architecture. All feature code lives in one folder. Add Clean Architecture principles only when complexity justifies it, like rich domain models or separate infrastructure concerns. ↳ Small focused classes save tokens with AI. ↳ AI agents find your feature code much faster when it's all in one place. 𝟮. 𝗠𝗶𝗰𝗿𝗼𝘀𝗲𝗿𝘃𝗶𝗰𝗲𝘀 𝗙𝗿𝗼𝗺 𝗗𝗮𝘆 𝟭 ❌ Distributed transactions, debugging hell, deployment chaos. ✅ Start with a Modular Monolith. Extract microservices only when you feel real scaling pain. Most apps die with 100 users, not at 1 million. The best microservices are born from a Modular Monolith. 𝟯. 𝗠𝗮𝗽𝗽𝗶𝗻𝗴 𝗟𝗶𝗯𝗿𝗮𝗿𝗶𝗲𝘀 ❌ AutoMapper, Mapster and Mapperly hide your mapping logic. ❌ You lose direct navigation and fight library quirks for hours. ✅ Use manual mapping. Full control, direct navigation, easy debugging. With AI coding agents, mapping code takes seconds to write. Mapping libraries don't save you any time anymore. 𝟰. 𝗠𝗲𝗱𝗶𝗮𝘁𝗥 𝗘𝘃𝗲𝗿𝘆𝘄𝗵𝗲𝗿𝗲 ❌ No direct navigation from endpoint to handler. ❌ Redundant command classes and interfaces just to satisfy the pattern. ✅ Use plain handler classes without interfaces. Inject and call them directly from your endpoints. Same separation of concerns. Less code. Full IDE navigation in one click. 𝟱. 𝗘𝗙 𝗖𝗼𝗿𝗲 𝗪𝗶𝘁𝗵 𝗥𝗲𝗽𝗼𝘀𝗶𝘁𝗼𝗿𝗶𝗲𝘀 ❌ EF Core is already a Repository and a Unit of Work. ✅ Use DbContext directly in your application handlers. Stop creating wrappers around wrappers. You only hide the real power of EF Core: LINQ, change tracking, and projections. 𝟲. 𝗨𝗻𝗶𝘁 𝗧𝗲𝘀𝘁𝘀 𝗮𝘀 𝗗𝗲𝗳𝗮𝘂𝗹𝘁 ❌ Heavily mocked unit tests give false confidence. ❌ They pass while your real app crashes in production. ✅ Make integration tests your default. Use WebApplicationFactory + TestContainers to verify the real endpoint → real database flow. Your config, DI, middleware, and migrations get tested too. 📌 My rule: Don't add complexity unless the project actually needs it. Most "best practices" are someone else's solution to someone else's problem. Build for today. Add layers tomorrow only if real pain shows up. Start simple, leaving room for extension in the future. The last trend I dropped was MediatR. Which of these trends will you drop first? —— ♻️ Repost to help other .NET devs ditch trends that introduce unnecessary complexity ➕ Follow me ( @AntonMartyniuk ) to improve your .NET and Architecture Skills
Anton Martyniuk tweet media
English
9
18
106
3.6K
OrcDev
OrcDev@orcdev·
Open source tool that feels illegal to be free, part 47 - chanhdai[.]com A pixel perfect developer portfolio and shadcn registry with reusable components, MDX content, AI-ready docs, SEO, PWA support, and beautiful design details. @iamncdai 🔥
English
7
5
41
4.3K
Anton Martyniuk
Anton Martyniuk@AntonMartyniuk·
@mjovanovictech I really like that we have one line of code to use FusionCache as HybridCache that fixes these issues with multiple instances, just with one Fusion Cache NuGet package: `ZiggyCreatures.FusionCache` `builder.Services.AddFusionCache() .AsHybridCache();`
English
1
0
7
699
Milan Jovanović
Milan Jovanović@mjovanovictech·
HybridCache is great. But it does not magically solve cache invalidation across multiple app instances. This is the trap. You add HybridCache because you want the best of both worlds: - fast local memory cache - shared distributed cache - stampede protection - simple API And for a single instance, that works beautifully. But once you run multiple nodes behind a load balancer, the local cache becomes a consistency problem. User updates their profile on Server 1. Server 1 updates the database and clears its local cache. Then the next request hits Server 2. Server 2 still has the old value in memory. Now the user sees stale data. Short TTLs can reduce the problem, but they don’t really solve it. They just trade correctness for more Redis/database calls. The better approach is a backplane. When one node changes data, it publishes a cache invalidation message. Every other node receives it and removes the same key from its local cache. Redis Pub/Sub is a great fit for this because many teams already use Redis as the L2 cache. The flow becomes: 1. Update the database 2. Publish the invalidation key to Redis 3. All nodes receive the message 4. Each node removes the key from its local HybridCache 5. The next request fetches fresh data Now you keep the speed of local caching without letting every node live in its own reality. And if you don’t want to build this yourself, FusionCache already has a backplane built in. I wrote a full breakdown of the problem, the Redis Pub/Sub solution, and how it fits with HybridCache: milanjovanovic.tech/blog/solving-t…
Milan Jovanović tweet media
English
2
16
93
4.6K
Milan Jovanović
Milan Jovanović@mjovanovictech·
Postgres is quietly becoming my favorite AI database. I built semantic search in .NET using pgvector, Ollama, and Aspire. Embeddings, indexing, cosine similarity, all inside Postgres. Watch it here: youtu.be/c_YZazjQrNs
YouTube video
YouTube
Milan Jovanović tweet media
English
3
16
144
5.9K
Milan Jovanović
Milan Jovanović@mjovanovictech·
You don’t need a Dockerfile to containerize a .NET app. That still feels weird to say. For years, the default path looked like this: - write a multi-stage Dockerfile - pick the right SDK image - pick the right runtime image - copy the project file first - restore dependencies - copy everything else - publish the app - copy the output into the final image - hope you didn’t break layer caching It works. But it’s also a lot of ceremony for a typical ASPNET Core API or worker service. The .NET SDK can publish directly to a container image: dotnet publish --os linux --arch x64 /t:PublishContainer That’s it. The SDK builds the app, selects the right base image, creates the container image, and loads it into your local Docker or Podman daemon. You can still customize the important parts: - image name - tags - base image - ports - registry - CI/CD flow Most of it lives in your `.csproj`, where .NET developers already spend their time. I still use a Dockerfile when I need native system dependencies, complex build steps, or extra tools inside the image. But for most web APIs and background services? The SDK approach is enough. I wrote a full walkthrough of containerizing .NET applications without a Dockerfile, including image customization, GitHub Container Registry, GitHub Actions, and deploying to a VPS: milanjovanovic.tech/blog/container…
Milan Jovanović tweet media
English
9
35
252
11.7K
Anton Martyniuk
Anton Martyniuk@AntonMartyniuk·
𝗧𝘄𝗼 𝗱𝗲𝘃𝗲𝗹𝗼𝗽𝗲𝗿𝘀 𝗿𝗲𝘃𝗶𝗲𝘄𝗲𝗱 𝘁𝗵𝗲 𝘀𝗮𝗺𝗲 𝗰𝗼𝗱𝗲: One got fired One got promoted The difference? How they gave feedback 👇 "You should have used Strategy Pattern" ❌ "What do you think about using Strategy Pattern here?" ✅ "Where are the tests? Do you even care about quality?" ❌ "Let's make sure we have test coverage for this feature" ✅ "This code is a mess" ❌ "I see some opportunities for simplification here" ✅ "Why didn't you just do X?" ❌ "Have you considered X as an alternative?" ✅ "This code will crash, fix it" ❌ "Let's focus on improving error handling here" ✅ "Only a junior would write this" ❌ "Here's a pattern that might help make this clearer" ✅ "You're doing this completely wrong" ❌ "Here's an approach that might be more maintainable" ✅ "Change this to use async/await" ❌ "Could we refactor this to use async/await?" ✅ "This function is stupid" ❌ "Can we simplify this function to improve clarity?" ✅ "This is obviously inefficient" ❌ "I noticed potential performance implications here" ✅ "Your code is hard to understand" ❌ "This section could use more documentation" ✅ "Never use global variables" ❌ "Consider using dependency injection instead" ✅ "You're not following standards" ❌ "Let's align this with our team standards" ✅ "This will cause bugs" ❌ "We might prevent edge cases by adding validation" ✅ "Why is this so complicated?" ❌ "Could we break this down into smaller functions?" ✅ "Fix this ASAP" ❌ "Could we prioritize addressing this?" ✅ "Your spacing is a disaster. Fix it before I look further" ❌ "Let's focus on the logic first, then polish spacing later" ✅ "What a mess. Nobody can review 2,000 lines at once" ❌ "Can you split this into smaller PRs?" ✅ "Stop using your patterns. Mine are the only right way" ❌ "Which pattern do you think fits best here?" ✅ "Change this name or I'm blocking the PR" ❌ "Approved — fix naming in a follow-up" ✅ "This PR has zero context. Am I supposed to guess?" ❌ "Can you add scope and risks in the PR description?" ✅ "You changed behavior and forgot the docs" ❌ "Can we update the README with usage notes?" ✅ Remember during code reviews: ✅ Focus on the code, not the person ✅ Suggest, don't command ✅ Explain the why ✅ Be constructive ✅ Stay positive Which toxic review comment have you received? Share below 👇 — ♻️ Repost to help others create a better code review culture ➕ Follow me ( @AntonMartyniuk ) to improve your .NET and Architecture Skills
Anton Martyniuk tweet media
English
8
13
47
2.5K
Milan Jovanović retweetledi
Pavle Davitkovic
Pavle Davitkovic@pavle_dav·
8 years as a software engineer has taught me a lot. But one thing has stood out: Some of the best knowledge comes from blogs. That’s why I picked a few of my favorite blogs from the newsletters I follow. They are created by awesome people: 1. @mjovanovictech 2. @TheCodeMan__ 3. @NikkiSiapno 4. @AntonMartyniuk 5. @RaulJuncoV 6. @milan_milanovic I warmly recommend subscribing if you haven’t already. You’ll gain a lot of valuable knowledge fast. ♻️ Repost to help others. ➕ Follow me(@pavle_dav) for more posts like this.
Pavle Davitkovic tweet media
English
6
13
78
7.4K
Milan Jovanović
Milan Jovanović@mjovanovictech·
Changing a password hashing algorithm sounds simple. Just replace the old one with the new one, right? Not quite. Password hashes are one-way. You can’t take an old PBKDF2 hash and convert it to Argon2. And if you replace the hasher implementation too aggressively, existing users can get locked out. New users work fine. Existing users enter the correct password. But the new hasher tries to verify an old hash. Verification fails. Now your “security improvement” is a production incident. The safer approach is migration on login: 1. Try the new algorithm first 2. If that fails, fall back to the legacy algorithm 3. If legacy verification succeeds, log the user in 4. Re-hash the password with the new algorithm 5. Save the upgraded hash Future logins now use the new path. No batch migration. No forced password reset for every user. No downtime. A few production details matter: - Store which algorithm was used - Track how many users still use the legacy format - Put the migration write behind a feature flag - Remove the legacy path once the migration is done The bigger lesson: A hashing upgrade is not just an auth change. It’s a zero-downtime data migration. I wrote a practical walkthrough using password hashing as the example: milanjovanovic.tech/blog/a-practic…
Milan Jovanović tweet media
English
5
8
63
3.9K