30 Sep 2024 #todayilearned #csharp
Repositories are the least SOLID part of our codebases.
When we work with Domain-Driven Design, we take care of our business domain and forget about our data-access layer. We end up dumping, in a single interface, every combination of methods and parameters to retrieve our entities from the database. This way, we break the Single Responsibility Principle and Interface Segregation Principle. You see? They’re the least SOLID part.
Our repositories become so bloated that to use one specific method from a repository, we end up depending on a huge interface with lots of other single-use methods. GetOrdersById
, GetOrdersByDate
, GetLineItemsByOrderId
…
The Specification Pattern Simplifies Repositories
With the Specification pattern, we extract the “query logic” to another object and away from our repositories.
Instead of making our repositories more specific by adding more methods (_repo.GetOrderById(123456)
), the Specification pattern makes repositories more general (_repo.FirstOrDefault(new OrderById(123456))
).
Think of a Specification as the query logic and the query parameters to retrieve objects.
Specifications make more sense when using Domain-Driven Design. With Specifications, we encapsulate the LINQ queries, scattered all over our code, inside well-named objects that we keep inside our Domain layer.
Here’s how to use the Ardalis.Specification NuGet package to create a specification and retrieve a list of movies by their release year:
using Ardalis.Specification; // <--
using Ardalis.Specification.EntityFrameworkCore; // <--
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped(typeof(Repository<>)); // <--
builder.Services.AddDbContext<MoviesContext>(options =>
options.UseInMemoryDatabase("MovieDb"));
var app = builder.Build();
app.MapGet("/movies/{releaseYear}", async (int releaseYear, Repository<Movie> repo) =>
{
var byReleaseYear = new MoviesByReleaseYear(releaseYear); // <--
var movies = await repo.ListAsync(byReleaseYear); // <--
// As an alternative, with Ardalis.Specification we can
// use a specification directly with a DbContext:
//var movies = await aDbContext.Movies
// .WithSpecification(byReleaseYear)
// .ToListAsync();
return Results.Ok(movies);
});
app.MapPost("/movies", async (Movie movie, Repository<Movie> repo) =>
{
await repo.AddAsync(movie);
await repo.SaveChangesAsync();
// Or, simply with a DbContext:
//await aDbContext.Movies.AddAsync(movie);
//await anyDbContext.SaveChangesAsync();
return Results.Created($"/movies/{movie.Id}", movie);
});
app.Run();
public class MoviesByReleaseYear : Specification<Movie>
// ^^^^^
{
public MoviesByReleaseYear(int releaseYear)
{
Query
.Where(m => m.ReleaseYear == releaseYear)
.OrderBy(m => m.Name);
}
}
public record Movie(int Id, string Name, int ReleaseYear);
public class Repository<T> : RepositoryBase<T> where T : class
// ^^^^^
{
public Repository(MoviesContext dbContext) : base(dbContext)
{
}
}
public class MoviesContext : DbContext
{
public MoviesContext(DbContextOptions<MoviesContext> options)
: base(options)
{
}
public DbSet<Movie> Movies { get; set; }
}
Ardalis.Specification provides a RepositoryBase<T>
class that wraps our DbContext
object and exposes the database operations using Specification objects. The ListAsync()
receives a specification, not an IQueryable
object, for example.
Our Repository<T>
is simply a class definition without query logic. Just a couple of lines of code.
Now the query logic is inside our MoviesByReleaseYear
. Ardalis.Specification translates those filtering and ordering conditions to the right chain of Entity Framework Core methods.
Our repositories are way simpler and the query logic is abstracted to another object.
A Naive Implementation of the Specification Pattern
The best way to understand a piece of code is to recreate a minimal version.
Here’s a naive and bare-bones implementation of an in-memory repository that filters a list using a specification object:
var movies = new[]
{
new Movie("Terminator 2", 1991),
new Movie("Totoro", 1988),
new Movie("Saving Private Ryan", 1998)
};
var repo = new InMemoryRepo<Movie>(movies); // <--
var found = repo.List(new ByReleaseYearSpec(1998)); // <--
// Output:
// [ Movie { Name = Saving Private Ryan, ReleaseYear = 1998 } ]
public record Movie(string Name, int ReleaseYear);
public class ByReleaseYearSpec : Spec<Movie> // <--
{
public ByReleaseYearSpec(int releaseYear)
{
Where(movie => movie.ReleaseYear == releaseYear);
}
}
public abstract class Spec<T> // <--
{
public Func<T, bool>? Predicate { get; set; }
public void Where(Func<T, bool> predicate)
{
Predicate = predicate;
}
}
public class InMemoryRepo<T>
{
private readonly IEnumerable<T> _items;
public InMemoryRepo(IEnumerable<T> items)
{
_items = items;
}
public IEnumerable<T> List(Spec<T> spec)
// ^^^^
{
var evaluator = new SpecEvaluator();
return evaluator.ApplySpec(_items, spec).ToList();
}
}
public class SpecEvaluator
{
public IEnumerable<T> ApplySpec<T>(IEnumerable<T> items, Spec<T> spec)
{
// The original implementation uses some "evaluators" to:
// 1. Check if the spec has a certain shape and
// 2. Apply that shape to the input "query"
//
// For example, WhereEvaluator checks if the spec has a Where clause and
// applies it.
//
// This SpecEvaluator would be a Composite of smaller
// evaluators that look for a certain shape
if (spec.Predicate != null)
{
return items.Where(spec.Predicate);
}
return items;
}
}
All the magic is inside the InMemoryRepo
and the SpecEvaluator
classes.
In the original implementation, the SpecEvaluator takes the parameters from the filters inside our specification (like Where, OrderBy, Skip, and Take) and apply them using EntityFramework Core methods into the IQueryable
object that represents our database query.
Voilà! That’s how to use the Specification pattern to make our repositories more SOLID. With the Specification pattern, our repositories have a slim interface and a single responsibility: to turn specifications into database calls.
For more C# content, check how to create test data with the Builder pattern, how to use the Decorator pattern with a real example, and how to use the Pipeline pattern: An assembly line of steps.
25 Sep 2024 #asp.net
.NET 6.0 replaced the Startup class with a new hosting model and a simplified Program.cs file.
The Startup class is still available in newer versions. If we’re migrating a pre-.NET 6.0 project, the .NET upgrade assistant tool does the work while keeping the Startup class.
Here are 3 alternatives to handle with the Startup class when migrating to newer versions:
1. Official Docs Approach
Newer versions of ASP.NET Core work perfectly fine with the old Program.cs file and Startup class, we can choose to do nothing and keep them.
Here’s an old-style Program class:
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace AnOldStyleAspNetCoreProject;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
And a Startup class:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace AnOldStyleAspNetCoreProject;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
Nothing fancy. A simple API project.
If we want to keep the Startup class, here’s what Microsoft official docs show to make the Startup class work with the new hosting model and the simplified Program.cs:
var builder = WebApplication.CreateBuilder(args);
// vvvvv
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);
// ^^^^^
var app = builder.Build();
startup.Configure(app, app.Environment);
// ^^^^^
app.Run();
We created a new instance of Startup inside the new Program.cs file.
2. Hybrid Approach
If we really want to ditch the Startup class, Andrew Lock recommends a hybrid approach in his blog:
Turn the methods from the Startup class into private methods in the new Program.cs file.
var builder = WebApplication.CreateBuilder(args);
ConfigureServices(builder.Services);
// ^^^^^
var app = builder.Build();
Configure(app, app.Environment);
// ^^^^^^
app.Run();
// This method used to be in Startup.cs
static void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
// This method used to be in Startup.cs too
static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
3. Do-It-Yourself Approach
And if we want our Program.cs to look like the newer ones, there’s no automatic tool for that (at least I couldn’t find one in my Googling session). We have to copy the contents of the Startup class into the Program.cs file, by hand:
var builder = WebApplication.CreateBuilder(args);
// Put here what you had in ConfigureServices...
builder.Services.AddControllers();
var app = builder.Build();
// Put here what you had in Configure...
app.UseAuthorization();
// Before:
//
// app.UseRouting();
// ASP0014: Suggest using top level route registrations instead of UseEndpoints
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllers();
//});
//
// After:
app.MapControllers();
app.Run();
We cut and pasted the content of Startup inside the right sections of the new Program.cs. This time, we get some warnings about deprecated methods and their alternatives.
Voilà! If you’re a lazy developer like me, do nothing or go with the approach from the official docs. Otherwise, go with any of the other two options.
23 Sep 2024 #writing
Last week, Syed, one of my email subscribers, shared his struggles with writing online.
Here’s an edited version of Syed’s email:
I wanted to start writing about my debugging journey of the things I had been stuck with long time and then solving it finally… But I couldn’t continue as I thought my website wasn’t the best with SEO and no one may read it on my website. Then I shifted to Twitter and later, due to fewer engagements, I couldn’t continue there either. Well no I think maybe writing for some well-known forums might be the way.
For Syed and you that want to start writing as a software developer:
Don’t Create, Document
Start by sharing what you do and what you learn. That’s a good start.
Writing online is like keeping a public time capsule. If you don’t know what to add to your time capsule, follow the 20-minute rule: if something takes you more than 20 minutes to figure out, write about it.
Write about your learning and the problems you’re solving at work. Probably, the next time you’re googling something, you will find your own writing. That’s magical.
If You Want to Start Writing, Don’t Start Your Own Blog
And don’t code your own blogging engine either.
When we want to start writing, we fall back to doing what we know best — coding — and start by coding a blogging engine. That’s where writing and blogging die.
The best place to start is on “social blogs.” Platforms for long-form content with readers and a distribution mechanism. This way, you don’t have to “chase” readers with SEO tricks and you’ll have faster feedback.
If you don’t know where to start, go with dev.to or Medium. I can’t recommend dev.to enough. It’s a beginner-friendly and welcoming platform for coders.
Once you start on a social blog, keep your blog as your main hub or a portfolio of your favorite posts. That’s what I do.
We All Started With Zero Readers and Followers
At the beginning, writing can feel lonely.
I wrote my first online piece in 2018 and nobody read it. Maybe only one or two of my coworkers. I saw my blog analytics going from ~10 views per month to eventually ~1000s in a matter of years. Yes, you read that right. Years.
Focus on writing your first 10 posts and keep trying and improving.
Here’s when the Show Your Work attitude keeps us writing in the long run. And like any other infinite game, you only lose if you stop playing.
Write for yourself and for sure others people will find it useful too.
SEO Is Another Skill to Master
If you go with a social blog, the platform does the SEO part for you. You don’t have to worry about it.
Search engines keep changing their rules with algorithm updates. These days, it seems Google favors Reddit posts instead of personal blogs.
You’ll be in a dead end if you try to chase every SEO update. Write for humans because search engines like it when you do that.
Instead of trying SEO tricks, go with these rules:
- Write to answer a query people might search for in a search engine.
- Make your posts easy to read for humans: Don’t use big chunks of text and use subheaders.
- Link back to other posts using keywords: Don’t use “click here” or “see more.”
You see? Another skill to master. Go with a social blog.
Parting Thought and Challenge
I owe my career growth to a couple of skills: apart from coding, learning English and writing online.
After blogging for more than five years, writing online has opened doors here and there. I made my first money on the internet thanks to my blog, for example.
Even if you don’t make any money writing, it will give you clear thinking.
And if you’ve made it this far, here’s my challenge: create an account on dev.to and write your first 4 posts there and see where they take you. If you accept the challenge, contact me and share your first post.
Write as if no one is reading and then keep writing because you don’t know who’s reading.
16 Sep 2024 #writing
A Sunday evening free of dopamine ended up being a lesson on writing and blogging.
I started googling about note-taking and landed on Herbert Lui’s blog after finding his lessons after 800 Zettelkasten notes.
In case you don’t know Herbert Lui’s work, he’s a writer, editorial director, and book author. He wrote Creative Doing, a book with exercises and prompts for writers and creatives.
After binge-reading his blog, I learned these lessons from him.
1. Don’t Start From Scratch
Be a DJ producer of ideas instead.
Instead of trying to come up with original ideas every time, mix and build on your past and maybe forgotten ideas.
Don’t start from scratch again. Go back to one of your old ideas or writings and give it a fresh lick of paint.
2. Prefer Quantity Over Quality
If you want to improve at something, go for quantity.
That’s the key finding of the story of the pottery class: A teacher divided his pottery class into two halves. The first half was graded on the quantity of pottery produced and the other half on the quality instead. At the end of the class, the half graded on quantity did best. Their effort was to produce something and quickly move on to the next piece, taking the feedback from past repetitions.
If you need ideas to go for quantity, do a 100-day challenge: finish something every day for 100 days. Herbert wrote for 100 days and shared his lessons.
Quality comes after quantity.
3. You Don’t Run Out of Ideas if You Know Where To Look
When you think you’re running out of ideas, go through your favorite blogs, YouTube channels, and your social feeds and write a reaction post for a piece you liked.
That’s only one of the ideas I use to never run of writing ideas.
4. Don’t Write Masterpieces, Plant Seeds Instead
A post doesn’t have to be a 2,000-word masterpiece.
To remove the pressure of writing masterpieces every time, consider a blog like a garden of ideas instead of a finished and polished product. You can always expand a past idea on a new piece.
Most of Herbert’s posts only have a headline and one main idea. You’re free to break the rule of writing introductions and conclusions. That’s something I also noticed by reading Seth Godin’s blog. They write the main idea naturally after the headline without an introduction.
Finding Herbert’s blog inspired me to continue writing. Herbert doesn’t seem to have a “niche.” Maybe everything he writes falls under the umbrella of writing and creativity.
Don’t have a niche and go for quantity. You don’t need to write masterpieces, only to document you journey.
09 Sep 2024 #writing
Running out of ideas is the greatest fear of new writers.
But don’t worry. You’re not running out of ideas. You have plenty of them. You need to be aware of the content you consume and have writing prompts to help you write.
Here are 3 tips to never run out of ideas to write:
1. Become a DJ of Ideas
There’s nothing new under the sun. And it has all been done before.
That’s relieving and reassuring. That removes the self-imposed pressure of coming up with new and original ideas.
Never start from scratch again. Develop your “new” ideas by remixing and expanding your past ideas or others’ ideas. Become a DJ producer of ideas.
If you think you’re running out of ideas, go through your posts, videos, or social feed and write a reaction post about one piece of content.
2. Follow the 20-Min Rule
If something takes you 20 minutes or more to figure out, write about it.
This is the prompt I’ve used to write some of my coding tutorials. Once I figure out how to do something after being stuck for a while, I write about it.
I wish I could credit the source of this rule. I remember learning about it from an old YouTube presentation about technical blogging that I can’t find anymore.
So if you have an aha moment, write about it. If you finally understood a hard subject, write about it.
By writing about your learning struggles, you will reinforce and document your learning.
3. Follow the 3-Strike Rule
If a subject or idea comes up at least three times around you, write about it.
I found this idea on Shawn Wang (swyx)’s blog in the context of technical blogging. But it can be easily extended outside the coding world too.
If another client asks you the same question, write about it. And if you give the same piece of advice to someone, guess what? Write about it.
With the 3-strike rule, you’re also saving your keystrokes. After 3 strikes, the next time the same subject comes up, just share a link to your writing.
Now, you’re not running out of ideas again. It’s impossible. Notice the content you’re consuming and write about it.