A weak or anaemic domain model just means your "domain objects" are DTOs, with no behaviour. What you've basically got is the Transaction Script pattern, where DTOs are loaded, modified and saved again. CRUD, to put it another way. This is fine for a lot of apps, which don't have complex enough rules to benefit from a DDD-approach.
Domain objects should encapsulate behaviour. That is their essence. Any public state (it is possible to have no public getters or setters) should be readonly, and if you want to mutate state, you call a method that relates to a use-case/business requirement.
All your "domain logic" should be in these classes. Domain logic is just a fancy name to describe the rules and operating parameters of your chosen domain. Be it banking, retail, HR etc. When your domain experts explain the "Pay By Card" user-case and tell you "We can't open the till until the PDC machine has contacted the bank.", that's a business rule/invariant/piece of domain logic.
You would normally assemble you domain objects (made up of Entities and Value Objects) into Aggregates, which define a boundary within which a given set of rules must be satisfied. The Entity that is the root of this domain object graph is known as the Aggregate Root, and it is only to Aggregate Roots that other objects may hold references. In your case, User
is an Entity, and as it's the only object in the Aggregate it is also the Aggregate Root. So for example:
public class User // Entity and also the Aggregate Root
{
private readonly IList<Friendship> _friends = new List<Friendship>();
public void Befriend(User user)
{
_friends.Add(new Friendship(/* Date? */, user));
}
public class Friendship // Entity
{
// ... Date?
private User _friend { get; private set; }
public Friendship(/* Date? */, User friend)
{
_friend = friend;
}
}
}
This isn't a great example really because in theory you'd need to call this for each of two friends in a pair, but any transaction should only carry out one operation, not two. In this case, you introduce the concept of Process Managers. These are yet more objects that deal with the coordination of what is essentially a long-running transaction (where two friends become friends with each other). You'd probably create a Friendship (as an Aggregate Root) and it's creation would spawn some sort of event-driven process where the friends involved are loaded, befriended, and saved.