Beyond the Hype: Concerns and Practical Approaches for Records
Reason
In the last weeks, I saw a LinkedIn post in which someone was explaining the benefits of records. He was talking about conciseness and readability, immutability and how perfect they are for DTOs. His example looked like this:
public class CreateUserResponse
{
public int Id {get;set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public string Email {get;set;}
public string UserName {get;set;}
}
public record CreateUserResponse(
int Id,
string FirstName,
string LastName,
string Email,
string UserName);
Concerns
If the class has no logic, and we are talking about transferring data objects, it’s fine. My problem is when juniors or people with not that much experience on the project make their personal quest to change everything possible into records.
A second concern is when logic appears in there. I don’t even think towards records when if I have logic in there. I don’t say you can’t have it (probably a small validation I would accept), but having complex business logic in there, for me is a big question mark, if you’re not just “forcing” your team to have a record instead of an old class.
The third concern is about the number of arguments. I tried to take it seriously and think about a use case like this. Probably is something personal, or maybe I still have some flashbacks from the Clean Clode book (written by Robert C. Martin), but 5 is a pretty big number. When you have 5 arguments, the 6th will appear, and after that, the 7th, and it will stop when a new guy comes in, and probably during his onboarding period, he will start refactoring this. Having so many arguments is a problem when you think about readability and maybe even maintainability.
Let’s say I really have this scenario and I really need records, probably I will take the next approaches:
Option 1:
public record User
{
public int Id { get; init; }
public string FirstName { get; init; }
public string LastName { get; init; }
public string Email { get; init; }
public string Username { get; init; }
}
//And use it like this:
var user = new User
{
Id = 1,
FirstName = "Cosmin",
LastName = "Vladutu",
Email = "my@test.com",
Username = "cosmin.vladutu"
};
This way I won’t have a long list of parameters, but you will say it looks like a class.
Option 2:
public record FullName(string FirstName, string LastName);
public record User(int Id, FullName Name, string Email, string Username);
//And use it like this:
var user = new User(1, new FullName("Cosmin", "Vladutu"), "my@test.com", "cosmin.vladutu");
In theory, I like this more because it feels that I get an increased reusability and I get rid of one argument.
Option 3:
public record User
{
public int Id { get; init; }
public string FirstName { get; init; }
public string LastName { get; init; }
public string Email { get; init; }
public string Username { get; init; }
public class Builder
{
private User _user = new();
public Builder SetId(int id) { _user = _user with { Id = id }; return this; }
public Builder SetFirstName(string firstName) { _user = _user with { FirstName = firstName }; return this; }
public Builder SetLastName(string lastName) { _user = _user with { LastName = lastName }; return this; }
public Builder SetEmail(string email) { _user = _user with { Email = email }; return this; }
public Builder SetUsername(string username) { _user = _user with { Username = username }; return this; }
public User Build() => _user;
}
}
//And use it like this:
var user = new User.Builder()
.SetId(1)
.SetFirstName("Cosmin")
.SetLastName("Vladutu")
.SetEmail("cosmin@example.com")
.SetUsername("cvladutu")
.Build();
It depends on the team, but I know a few guys who LOVE having fluent API, and builder pattern. This way you gain more advantages: I keep my team happy (if they are into this kind of stuff) and better flexibility.
A disadvantage is the fact that the arguments aren’t initialized together which might be a problem for you, and the fact that it feels more complicated than it needs to be.
Personal opinion
If the complexity grows too much (or the number of arguments) you should think about what you can do and how you can refactor. Maybe the object is doing too much and should be split, maybe you should change into a class and see what you can do in that direction, and so on.
Don’t stick to records just because they are cool, or new. Design your code carefully. In the end, you should have a balance between simplicity, maintainability, and usability.