Let me introduce you into a minimalistic and made-up case with real-case chronological order, updates and problems.
User with ID only
I had a system, and it had User
entity with ID
column only. So, there was a class User
and SQL table Users
:
[Table("Users")] // database table
public class User
{
[Key]
[Column("Id")] // database column
public long Id { get; set; }
}
New properties
One day, a product owner comes to me and asks me to introduce 4 new properties:
- birthday
- age maturity (18 y.o. and above)
- subscribtion end (paid until) date
- is active, which means that subscription has not expired yet
Of course, at first I propose to not store "age maturity" and "is active", since it is a secondary evaluated value.
However, business analyst tells me: OK, do it your way, but we will need to see \ export table of all users with their maturity and activeness statuses, with up to 10000 users.
Now, I see 3 solutions and cannot decide which is better in terms of summarized assessment of architecture, performance and future supportability.
Possible solutions
1. Evaluable properties in domain model
[Table("Users")] // database table
public class User
{
[Key]
[Column("Id")]
public long Id { get; set; }
[Column("Birthday")]
public DateTime Birthday { get; set; }
public bool IsMature
{
get { return (DateTime.Now - Birthday).Years > SystemCfg.MaturityAge; }
}
[Column("PaidUntil")]
public DateTime? PaidUntil { get; set; }
public bool IsActive
{
get { return PaidUntil.HasValue && PaidUntil.Value >= DateTime.Now; }
}
}
Pros:
- Actual values: values are always actual
- Business-logic placement: business-logic is stored in a single place
Cons:
- Performance: in real example,
PaidUntil
is actually a property which takes some time to be calculated, sometimes with web-requests, so calculating it for 10000 users dynamically can become dramatically slow in future
2. Database-stored properties, recalculated at sign in
[Table("Users")] // database table
public class User
{
[Key]
[Column("Id")]
public long Id { get; set; }
[Column("Birthday")]
public DateTime Birthday { get; set; }
[Column("IsMature")]
public bool IsMature
[Column("PaidUntil")]
public DateTime? PaidUntil { get; set; }
[Column("IsActive")]
public bool IsActive { get; set; }
}
// on sign in:
user.IsMature = (DateTime.Now - user.Birthday).Years > SystemCfg.MaturityAge;
user.IsActive = PaidUntil.HasValue && PaidUntil.Value >= DateTime.Now;
_userRepository.Update(user);
if (!user.IsActive)
return AuthResult.UserIsInactive;
Pros:
- Performance: values are only recalculated on sign in, which brings the best performance among these options
Cons:
- Business-logic placement: business-logic is stored in a single place, but model is now anemic
- Actual values: values are mostly not actual for report / export views, since are updated on user sign in
3. Database-stored properties, recalculated on schedule
Same model, but data is updated by a special Windows Service, which runs through my database and keeps data up to date.
Pros:
- Performance: values are calculated on a separate thread / application / server
- Actual values: values are guaranteed to be actual UNTIL update server becomes overloaded or down
Cons:
- Business-logic placement: business-logic is stored in a separate, non-domain application
- This approach sounds weirds
So, what approach should I use for this special case?
What approaches do you and people usually use?