Arguing immutability for the sake of immutability is considered harmful imo. Only make things immutable when they actually are immutable or when concurrency is at play (which it probably isn’t, 99% of all code is single threaded).
Arguing immutability for the sake of immutability is considered harmful imo. Only make things immutable when they actually are immutable or when concurrency is at play (which it probably isn’t, 99% of all code is single threaded).
In theory it sounds great to say "just prevent construction of bad states," but this is a complex problem in professional software that involves coordination between programmers, designers, and product managers to get the UI features into the right place where preventing the construction of bad states doesn't lead to a frustrating UI experience for users. It's often far simpler, more pragmatic, and less complex in terms of coupling in both software and humans to just accept whatever state the UI gives you and figure things out somewhere else where you aren't bothering your users so much.
This has nothing to do with the frontend/backend split since a server will receive arbitrary requests, not just the ones you hope you programmed the frontend to send.
> In theory it sounds great to say "just prevent construction of bad states," but this is a complex problem in professional software
Defeatism.
If you have a User in your domain, and your domain dictates that Users have a UserId, simply do not allow construction of a User without a UserId. What's a UserId? Is it a String, or is it a UUID? If you think the King James Bible translated into Chinese makes for a good UserId, then a String is a perfectly good representation for that. Otherwise pick something more suitable.
Any problem can be swept away with "you don't know what you're talking about", "you'll come around to my way of thinking when you're older", "X has nothing to do with Y", etc.
So, make things real with an example.
For instance when making a person or user you most likely only have their email when you initially create them and then it’s the users job to complete the rest of the data at a later time. Some might want to fill in their age or some might want to fill in their sex. You may never get this data because it’s optional, but when you set it you’ll want to validate it.
And as always in programming _it depends_. It depends on your workflow, requirements, and domain. Being dogmatic about anything is just going to result in worse code.
That's what I do, anyway.
But that's the entire value proposition of do-not-allow-construction-of-invalid-state!
If a User is actually a User, then you can say "the object is already constructed in a valid state".
If you decide to be 'less dogmatic' and instead construct Users-which-may-or-may-not-be-users-just-throw-an-ex-if-theyre-no-good, then you cannot say that the object is already constructed in a valid state.
In your case I would copy rather than mutate, unless I'm actually getting noticably higher profits from lower memory usage during the time between getting the object and GC. This hasn't happened to me yet.
class User {
Long? id;
String email;
String? name;
Integer? age;
Sex? sex;
Avatar? avatar;
User(String email) {
this.email = email;
}
}
record ProfileVm(String? name, Integer? age, Sex? sex, Avatar? avatar) {}
// some ui controller
var user = findById(id)
if (user == null) {
return notFound();
}
var vm = ProfileVm.fromJson(request.json());
user.setName(vm.name());
user.setAge(vm.age());
user.setSex(vm.sex());
user.setAvatar(vm.avatar());
user.save();
return ok();