3

最近、私はTDDをいじくり回し、SOLIDの原則に基づいてコーディングしています。次のようなシナリオがあります。

  • 1つは持つことができますIRecurringProfile、それは間隔で、例えば毎月のベースで一連の支払いを実行します
  • 支払いが完了しようとして失敗すると、にIRecurringProfileTransactionリンクされたaが作成されIRecurringProfilePayment、支払いが完了しなかったことを示します。
  • 支払いの失敗数が増加します。
  • 支払いを再試行し、別の失敗通知を送信する日時(支払いが失敗した場合)も更新されます。
  • 支払いの失敗数が最大しきい値に達すると(たとえば、3回失敗した場合)、IRecurringProfileは一時停止されます。
  • また、失敗するたびに、支払いが完了しなかったことをクライアントに通知する通知が送信され、再試行されます。

以下は私が作成したサンプルコードです。これは主に、定期的なプロファイル支払いを失敗としてマークするタスクを扱います。私は、コンストラクターの注入だけでなく、SOLIDの原則にも従おうとしました。これがこれらの原則またはプログラミングのベストプラクティスのいずれかに違反しているかどうかを知り、コードを改善できるようにあらゆる形式の精査の対象にします。このコードでは、ORMとしてNHibernateも使用しています。

public class RecurringPaymentMarkAsFailure
{
    private readonly IPaymentFailedNotificationSender paymentFailedNotificationSender;
    private readonly IRecurringProfileFailureNextNotificationDateUpdater failureNotificationDateUpdater;
    private readonly IRecurringProfileSuspender recurringProfileSuspender;

    public RecurringPaymentMarkAsFailure(IPaymentFailedNotificationSender paymentFailedNotificationSender, IRecurringProfileSuspender recurringProfileSuspender,
        IRecurringProfileFailureNextNotificationDateUpdater failureNotificationDateUpdater)
    {
        this.paymentFailedNotificationSender = paymentFailedNotificationSender;
        this.failureNotificationDateUpdater = failureNotificationDateUpdater;

        this.recurringProfileSuspender = recurringProfileSuspender;

    }

    private void checkProfileStatus(IRecurringProfile profile)
    {
        if (profile.Status != Enums.RecurringProfileStatus.Active)
        {
            throw new Exceptions.RecurringProfileException("This cannot be called when the profile is not marked as active");
        }
    }


    private void incrementFailureCount(IRecurringProfilePayment payment)
    {
        payment.FailureCount++;
    }

    public IRecurringProfileTransaction MarkPaymentAsFailed(IRecurringProfilePayment payment, string failureData)
    {
        using (var t = BeginTransaction())
        {
            checkProfileStatus(payment.RecurringProfile);


            IRecurringProfileTransaction transaction = payment.Transactions.CreateNewItem();
            transaction.OtherData = failureData;
            transaction.Status = Enums.RecurringProfileTransactionStatus.Failure;
            paymentFailedNotificationSender.CreateAndQueueNotification(transaction);
            failureNotificationDateUpdater.UpdateNextFailureNotificationDate(payment);
            incrementFailureCount(payment);

            if (payment.FailureCount >= payment.RecurringProfile.MaximumFailedAttempts)
            {
                recurringProfileSuspender.SuspendRecurringProfile(payment.RecurringProfile);
            }
            transaction.Save();
            t.Commit();
            return transaction;
        }

    }

}

-

ちなみに、この質問は、同様のトピックに関する私の最新の投稿を補足するものです。

4

1 に答える 1

3

私が見ているように、あなたにRecurringPaymentMarkAsFailureは2つの責任があるため、単一責任の原則に違反しています。支払いを失敗として行う責任に加えて、トランザクションを管理する責任も追加されます(これusing (BeginTransaction)は横断的関心事です。

システムには、ビジネスロジックを処理するこれらのようなクラスが多数あり、おそらくすべて(または多数)にまったく同じトランザクションコードがあります。代わりに、この動作をデコレータとして追加できるようにすることで、オープン/クローズド原則を順守することを検討してください。トランザクションはこのコードの最初で最後の操作であるため、これは十分に可能です。このデコレータの素朴な実装は次のようになります。

public class TransactionRecurringPaymentMarkAsFailureDecorator
    : RecurringPaymentMarkAsFailure
{
    private RecurringPaymentMarkAsFailure decoratedInstance;

    public RecurringPaymentMarkAsFailure(
        RecurringPaymentMarkAsFailure decoratedInstance)
    {
        this.decoratedInstance = decoratedInstance;
    }

    public override IRecurringProfileTransaction MarkPaymentAsFailed(
        IRecurringProfilePayment payment, string failureData)
    {
        using (var t = BeginTransaction())
        {
            var transaction = this.decoratedInstance
                .MarkPaymentAsFailed(payment, failureData);

            t.Commit();

            return transaction;
        }
    }
}

このデコレータを使用すると、次のようにクラスをラップできます。

var marker =
    new TransactionRecurringPaymentMarkAsFailureDecorator(
        new RecurringPaymentMarkAsFailure(
            /* dependencies */    
        ));

私が言ったように、この実装は少しナイーブです。おそらく、ラップする必要のあるクラスがたくさんあるためです。これは、各クラスが独自のデコレータを取得することを意味します。これは完全に固体ですが、乾燥していません。

代わりに、ユースケースを実行するすべてのクラスに、のような単一のジェネリックインターフェイスを実装させますICommandHandler<TCommand>TransactionCommandHandlerDecorator<TCommand>これにより、これらすべてのインスタンスをラップする単一のジェネリッククラスを作成できます。このモデルの詳細については、この記事を参照してください。一方、…私のアーキテクチャのコマンド側です

于 2012-10-17T08:45:52.947 に答える