Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DDD improvements #453

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

DDD improvements #453

wants to merge 5 commits into from

Conversation

max-arshinov
Copy link

  • IEntity is introduced for situations when an existing codebase needs to be refactored.
  • AggregateRoot and IAggregateRoot are essential for domain event handling. TDomainEvent instead of IDomainEvent makes it compatible with other libraries, for example with MediatR (which uses INotification)

@vkhorikov
Copy link
Owner

I'd really like to keep this library as lean as possible. Since there already is an Entity class, I'm fine with introducing the IEntity interface because you can't introduce your own IEntity otherwise, but let's not add AggregateRoot to the mix. This can be part of the consumer's code base.

Also, regarding IEntity, do you need all 4 public members?

public interface IEntity<TId>
{
	TId Id { get; }
	bool Equals(object obj);
	bool IsTransient();
	int GetHashCode();
}

It's fine if you do, but if you don't let's remove the unused members to keep the public API surface area as small as possible.

Also, I'm curious -- what's your use case for IEntity and IAggregateRoot? Why not use the base classes instead (Entity and AggregateRoot)?

@hankovich
Copy link
Collaborator

I'm not sure

bool Equals(object obj);
int GetHashCode();

are required. Since they are members of object, each object will have them. Inheriting IEntity<TId> from IEquatable<IEntity<TId>> looks more useful for me.

@max-arshinov
Copy link
Author

@hankovich you are right that Equals and GetHashCode are methods from the Object class. However, I wanted to emphasize Equals and GetHashCode must be overridden for all classes that implement IEntity. Technically this interface doesn't enforce this because all classes are derived from Object anyway. However, their existence in the interface definition makes this requirement more explicit. Perhaps, adding a summary to methods might help to highlight this requirement ever more.

@maxime-poulain
Copy link

maxime-poulain commented Oct 29, 2022

Merging this will change in the Entity class's API.
IsTransient() 's visiblity goes from private to public.
Dunno if it is something we want.

@hankovich hankovich mentioned this pull request Nov 17, 2022
@jeffward01
Copy link

Merging this will change in the Entity class's API. IsTransient() 's visiblity goes from private to public. Dunno if it is something we want.

Personally, I would not mind that. I work with EFCore pretty often, and I like to add checks when im persisting data to ensure that the Id is not null by using IsTransient() -- here is an example of this:

/// <summary>
		///     Adds or updates the given item.
		/// </summary>
		/// <typeparam name="TAggregateRoot">The type of the aggregate root.</typeparam>
		/// <typeparam name="TKey">The type of the ID.</typeparam>
		/// <param name="repository">The repository.</param>
		/// <param name="item">The item.</param>
		public static async Task AddOrUpdate<TAggregateRoot, TKey>(this IRepository<TAggregateRoot, TKey> repository, TAggregateRoot item)
			where TAggregateRoot : AggregateRoot<TAggregateRoot, TKey>
			where TKey : IComparable<TKey>, IEquatable<TKey>
		{
			Guard.Against.Null(repository);
			Guard.Against.Null(item);

			if(item.IsTransient)
			{
				await repository.AddAsync(item);
			}
			else
			{
				await repository.UpdateAsync(item);
			}
		}

By having the method IsTransient() exposed within CSharpFunctionalExtensions would encourage greater use and coupling of CSharpFunctionalExtensions which I think is a good thing, it allows the user to become more functionally oriented (Such as the method above is not Functional Programming Oriented).

@maxime-poulain
Copy link

maxime-poulain commented Dec 7, 2022

In my own entity base class I ship the IsTransient() method with a public visibility.

For cases like change tracking is disabled helps.

I suggest however that the method is virtual.

However, I agree with @vkhorikov not to have IAggegrateRoot part of the package. Should be your responsablity to have it in your common/shared Domain code.

Also from what I could read, he did not want even to have those Entity classes in this library.

@vkhorikov
Copy link
Owner

Yeah, we can make IsTransient() public and virtual, I don't see any issues with this.

@maxime-poulain
Copy link

Merging this will change in the Entity class's API. IsTransient() 's visiblity goes from private to public. Dunno if it is something we want.

Personally, I would not mind that. I work with EFCore pretty often, and I like to add checks when im persisting data to ensure that the Id is not null by using IsTransient() -- here is an example of this:

/// <summary>
		///     Adds or updates the given item.
		/// </summary>
		/// <typeparam name="TAggregateRoot">The type of the aggregate root.</typeparam>
		/// <typeparam name="TKey">The type of the ID.</typeparam>
		/// <param name="repository">The repository.</param>
		/// <param name="item">The item.</param>
		public static async Task AddOrUpdate<TAggregateRoot, TKey>(this IRepository<TAggregateRoot, TKey> repository, TAggregateRoot item)
			where TAggregateRoot : AggregateRoot<TAggregateRoot, TKey>
			where TKey : IComparable<TKey>, IEquatable<TKey>
		{
			Guard.Against.Null(repository);
			Guard.Against.Null(item);

			if(item.IsTransient)
			{
				await repository.AddAsync(item);
			}
			else
			{
				await repository.UpdateAsync(item);
			}
		}

By having the method IsTransient() exposed within CSharpFunctionalExtensions would encourage greater use and coupling of CSharpFunctionalExtensions which I think is a good thing, it allows the user to become more functionally oriented (Such as the method above is not Functional Programming Oriented).

Careful if you are using Guid for you ID's, for example, using context.DbSet.Add(SomeEntity); will generate for you a new ID.

This means that your IsTransient will say false but your entity has not been yet persisted !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants