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

In aggregate projection Apply() methods should not be called until Create() method has been called before. #3660

Open
alekbarszczewski opened this issue Feb 7, 2025 · 2 comments

Comments

@alekbarszczewski
Copy link

In aggregate projections currently Apply(event, projectionInstance) methods are being called whenever event happens even though Create(otherEvent) has never been called on a projection because otherEvent never happened. This also means that projectionInstance passed to Apply(event, projectionInstance) is initialized with defaults.

Expected behaviour:

Only Create(event) projection methods should create projection instance in db, and if the event that would trigger Create never happened then Apply(event, projection) methods should be ignored.

Tested on Marten 7.36.0.

public class AdditionalCardholdersToBeAssignedProjection 
    : MultiStreamProjection<AdditionalCardholdersToBeAssigned, string>
{
    public AdditionalCardholdersToBeAssignedProjection()
    {
        IncludeType<AdditionalCardholdersApproved>();
        Identity<IEvent<AdditionalCardholdersApproved>>(x => x.StreamKey!);
        
        IncludeType<SubscriptionStartedThroughApprovedApplication>();
        Identity<SubscriptionStartedThroughApprovedApplication>(x => x.MembershipApplicationId);
        
        IncludeType<MemberAssignedToSubscriptionThroughApprovedApplication>();
        Identity<MemberAssignedToSubscriptionThroughApprovedApplication>(x => x.MembershipApplicationId);
    }
}

public record AdditionalCardholdersToBeAssigned
{
    [Identity]
    public required string MembershipApplicationId { get; init; }
    
    // ...

    public static AdditionalCardholdersToBeAssigned Create(IEvent<AdditionalCardholdersApproved> approved)
    {
        return new AdditionalCardholdersToBeAssigned()
        {
            MembershipApplicationId = approved.StreamKey!,
            AdditionalCardholders = approved.Data.AdditionalCardholders,
            ReadyToBeAssigned = false,
        };
    }

    public static AdditionalCardholdersToBeAssigned Apply(SubscriptionStartedThroughApprovedApplication started, AdditionalCardholdersToBeAssigned cardholders)
    {
        // This gets called event though Create has never been called before and "cardholders" is just initialized with default values.
        // I would expect for the apply only be ran when projection instance exists.
        return cardholders with
        {
            ReadyToBeAssigned = true,
            SubscriptionId = started.SubscriptionId,
        };
    }

    // ...
}
@jeremydmiller
Copy link
Member

@alekbarszczewski The quick workaround for you would be to shift to CustomProjection as your baseline and just write explicit code. I'm not 100% sure this will be worked on any time soon.

@alekbarszczewski
Copy link
Author

alekbarszczewski commented Feb 7, 2025

Got it, well my workaround for now is that I actually check if some not-nullable property that should have been initialized in Create() is actually null during Apply - if yes I return null.

public static AdditionalCardholdersToBeAssigned? Apply(MemberAssignedToSubscriptionThroughApprovedApplication assigned, AdditionalCardholdersToBeAssigned cardholders)
    {
        if (cardholders.MembershipApplicationId is null) return null; // if not set means Create was not called before
        return cardholders with
        {
            AdditionalCardholders = cardholders
                .AdditionalCardholders
                .Where(x => x.ExistingMemberId != assigned.MemberId)
                .ToArray()
        };
    }

Also tried to declare method as:

public static AdditionalCardholdersToBeAssigned? Apply(
    MemberAssignedToSubscriptionThroughApprovedApplication assigned, 
    AdditionalCardholdersToBeAssigned? cardholders); // <-- nullable projection instance

But marten still initializes projection instance with default values (even if it does not exist in db).

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

No branches or pull requests

2 participants