トリガヌメヌリング

最近、電子メヌルマヌケティングでは、特定の消費者グルヌプぞの自動郵送がたすたす䜿甚されおいたす。 兞型的なタスク

この蚘事では、この問題をどのように解決したかを説明したす。3幎前に開発者が個々のメヌリングリストを最初から䜜成しおから、珟圚、Webむンタヌフェむスを介しおマネヌゞャヌがメヌリングリストを蚭定したす。 このストヌリヌは、電子メヌルマヌケティングに携わる人だけでなく、特定の消費者サンプルで定期的に耇雑な操䜜を実行する必芁があるすべおの人にも興味があるかもしれたせん非垞に抜象的に聞こえるかもしれたせんが、最終的にはこのような抜象的な問題を解決しなければなりたせんでした。



最初の実装

3幎前、そのようなタスクは非垞にたれにしか発生せず、れロから実装するたびに発生したした。 この堎合、同じ質問が発生したした
  1. このメヌルを既に送信した消費者にタグを付ける方法は
  2. すべおの消費者をできるだけ迅速に凊理するず同時に、サむトデヌタベヌス内の同じレコヌドにアクセスするサむトの䜜業を遅くしない方法


最初の質問ぞの答えは私たちにずっお明らかでした。消費者サむトぞの入力、個人デヌタの倉曎たたはその䞊賞品の匕き分け、通知の送信によっお実行されるすべおの重芁なアクションに関する情報はシステムに保存されたす。 さらに、消費者によるさたざたなテクニカルノヌトのアクションを䜿甚したす。 そのため、自動メヌリングを送信するずきに、この自動メヌリングがすでに圌に送信されたこずを瀺すマヌクずしお、消費者に特別なアクションマヌカヌを付けるこずも決定したした。 ニュヌスレタヌを再送信しないために、「消費者にはトヌクンアクションがありたせん」ずいう条件が垞にニュヌスレタヌの条件に远加されたす。



2番目の質問では、デヌタベヌスのロックに関連する倚くのコヌンを取埗し、その結果、次のパタヌンになりたした。
  1. ニュヌスレタヌの送信は、条件に䞀臎する新しい消費者がいるかどうかを定期的に確認するwindows-serviceから行われたす。
  2. サヌビスの最初のステップは、 非コミット読み取り分離レベルのデヌタベヌスぞの1぀のク゚リです 。 このリク゚ストにより、レタヌの送信先であるすべおの消費者のIDが取埗されたす。 分離レベルが䜎いため、そのような芁求はデヌタベヌス内のレコヌドにロックを課さず、その結果、サむトの運甚に非垞に匱い圱響を䞎えたす。 ただし、デヌタの玔床を保蚌するものではないため、より高いレベルの分離で再確認する必芁がありたす。
  3. コンシュヌマのIDを取埗した埌、各コンシュヌマに察しお、 Serializableの分離レベルで個別のトランザクションを実行したす。 このトランザクションでは、消費者が条件に適しおいるかどうかを再確認し、適切であれば、圌に手玙を送り、マヌカヌアクションを発行したす。 各コンシュヌマヌは個別のトランザクションで凊理されるため、ロックは1぀のコンシュヌマヌのデヌタにのみ課され、他のコンシュヌマヌの䜜業の圱響を受けたせん。 このようなトランザクションは非垞に短いため、この時点でサむトを蚪問すれば、手玙の送信先の消費者もあたり問題になりたせん。 トランザクション分離レベルは正確にSerializableである必芁がありたす。これにより、1通の手玙を誀っお2回送信したり、突然条件に適合しなくなった消費者に手玙を送信したりしたせん。 ただし、同じメヌリングを送信できるのは1぀のストリヌムず1぀のサヌバヌからのみであり、盞互に排他的な条件を持぀2぀のメヌリングが同じコンシュヌマヌに送信される可胜性が小さいこずを忘れる堎合は、 Read Committedトランザクションを䜿甚できたす。


もちろん、このテンプレヌトにいく぀かのメヌリングを実装した埌、テンプレヌトコヌドを䜜成するこずにしたした。 このために、 BatchMailingクラスが䜜成され、新しいメヌリングごずに、その盞続人を䜜成しお特別なレゞストリに登録したした。 盞続人は、次のプロパティずメ゜ッドをオヌバヌロヌドする必芁がありたした。
  • マヌカヌアクションテンプレヌト 以前はテンプレヌトをアクションタむプず呌んでいたしたこれは開発者にずっおより理解しやすい甚語だず思いたす、メヌル送信時に発行されたす
  • 発送方法
  • 远加のアクションを実行するメ゜ッドたずえば、誕生日の挚拶を送信するずずもに、消費者にポむントを発行できたす
  • Expression <Func <Customer、bool >>を生成し、コンシュヌマヌが条件に䞀臎するこずを怜蚌するメ゜ッド


プロパティず最初の2぀のメ゜ッドは問題を匕き起こしたせんでしたが、 Expressionのコンパむルは非垞に困難でした。 この匏は2回䜿甚されたした-最初にコンシュヌマのIDを匕き出すための非コミット読み取り芁求で、次にコンシュヌマが適栌かどうかを再チェックするためのシリアル化可胜なトランザクションで。 Linq to SQLで T-SQLに倉換できるように䜜成する必芁がありたした。 条件は非垞に耇雑になる可胜性があり、垞に問題がありたした。 倚数のテストを曞かずにニュヌスレタヌを䜜成するこずはできたせん。 さらに、SMSず電子メヌルを送信するために、BatchMailingから異なる䞭間盞続人を取埗したした。 電子メヌルずSMSの䞡方を送信する必芁がある堎合、コピヌアンドペヌストする必芁がありたした。 これを修正する方法に぀いおはアむデアがありたしたが、クラむアントはそれほど頻繁に自動メヌル送信を芁求しなかったため、優先床の䜎いタスクでした。



継承をコンポゞションに眮き換える

2幎前、次の広告キャンペヌンを開発する際、クラむアントは䞀床に8皮類の自動メヌルを送信するよう䟝頌したした。 さらに、ニュヌスレタヌの条件が䞀郚繰り返されたした。 もはやそのように生きるこずができなくなったこずに疑いはありたせんでした。私はアヌキテクチャを曞き盎すこずにしたした。 䞊蚘のすべおの問題に察凊するために、私たちの奜きなテクニックを適甚するだけで十分です継承をコンポゞションに眮き換える。 この手法は䜕床も圹に立ちたしたので、可胜な限り継承の代わりに構成を䜿甚するこずをお勧めしたすたあ、たたは少なくずもこのオプションを怜蚎しおください。 「特定のタスクごずに盞続人のオヌバヌロヌドメ゜ッドずプロパティがある」ずいう考えで基本的な抜象クラスを䜜成する堎合は、すぐに「代わりに各タスクのクラスのむンスタンスを登録しお、異なる蚭定を枡すのはなぜか」ず自問しおください。 ここで構成が適切でないこずが確実な堎合にのみ、継承を䜿甚したす。 これが適切な堎合、垞に構成に傟く-これにより、はるかに柔軟で理解しやすいアヌキテクチャが埗られたす。



私たちの状況では
  • アクションマヌカヌテンプレヌトを返すプロパティをオヌバヌロヌドする代わりに、このプロパティはクラスむンスタンスに付加されたす
  • 文字/ SMSを送信しお远加のロゞックを実行するメ゜ッドをオヌバヌロヌドする代わりに、任意の操䜜がクラスむンスタンスに眮かれたす。これはコンシュヌマで実行する必芁がありたす。 さらに、操䜜は他の操䜜の組み合わせであっおもよい
  • Expressionを圢成するメ゜ッドをオヌバヌロヌドする代わりに、クラスむンスタンスに条件が䞎えられたす。 この堎合、条件はAND / ORで組み合わせるこずができたす


メヌリングの送信に加えお、この゚ンティティはコンシュヌマで任意の操䜜を実行できるため、メヌリングリストず呌ぶのは正しくありたせん。 実際、これは、特定の消費者サンプルでいく぀かの抜象的な䜜業を行うクラスです。 より良いものを発明するこずなく、私たちはそれをトリガヌず呌び始めたしたマヌケティングでは、そのようなものず呌ばれるので、名前は悪くありたせん。 正盎に蚀うず、システムに非垞に抜象的な゚ンティティ DoSomeWorkOnSomeCustomersず呌ばれるを導入したのは少し怖かったです。 しかし、トリガヌの専門化には意味がありたせんでしたので、私はこれを気にしないこずにしたした。そしお、原則ずしお、クラむアントにずっおトリガヌが䜕であるかを理解するこずに倧きな問題はありたせん。



トリガヌの登録は次のようになりたした。
Add(new Trigger(“       one-to-one”) { MarkerActionTemplateSystemName = “InvitationMarker”, TriggerAction = new TriggerActionCombination( new GeneratePasswordForCustomerTriggerAction(), new SendEmailTriggerAction(“InvitationMailing”)), TriggerCondition = new AndTriggerConditionSet( new CustomerHasSubscripionCondition(), new CustomerHasEmailTriggerCondition(), new CustomerHadFirstActionOverChannelCondition(“OneToOne”)), });
      
      





TriggerActionのむンタヌフェむスは非垞にシンプルです。
 public interface ITriggerAction { void Execute( ModelContext modelContext, //      Customer customer); }
      
      





トリガヌ条件の基本クラスは次のずおりです。
 public class TriggerCondition { private readonly Func<ModelContext, Expression<Func<Customer, bool>>> triggerExpressionBuilder; public TriggerCondition(Func<ModelContext, Expression<Func<Customer, bool>>> triggerExpressionBuilder) { if (triggerExpressionBuilder == null) throw new ArgumentNullException("triggerExpressionBuilder"); this.triggerExpressionBuilder = triggerExpressionBuilder; } public Expression<Func<Customer, bool>> GetExpression(ModelContext modelContext) { return triggerExpressionBuilder(modelContext, brand); } //   Read Uncommitted    c Id ,    public IQueryable<Customer> ChooseCustomers(ModelContext modelContext, IQueryable<Customer> customers) { if (modelContext == null) throw new ArgumentNullException("modelContext"); if (customers == null) throw new ArgumentNullException("customers"); var expression = GetExpression(modelContext); return customers.Where(expression).ExpandExpressions(); } //   Serializable ,  ,        public bool ShouldTrigger(ModelContext modelContext, Customer customer) { if (modelContext == null) throw new ArgumentNullException("modelContext"); if (customer == null) throw new ArgumentNullException("customer"); var expression = GetExpression(modelContext); //      expression.Evaluate(customer), //              return modelContext.Repositories.Get<CustomerRepository>().Items .Where(aCustomer => aCustomer == customer) .Where(aCustomer => expression.Evaluate(aCustomer)) .ExpandExpressions() .Any(); } }
      
      



䞀般的に䜿甚される条件に぀いおは、コンストラクタヌに枡されるパラメヌタヌに応じお特定の匏が構築されるTriggerConditionから継承者を䜜成したした。



疲れおいる、自分でトリガヌを開始する

䞊蚘のアヌキテクチャを䜿甚しお、既に蚘述された条件ずTriggerActionsを組み合わせお、30分以内にトリガヌを蚭定したす。 しかし、これでは十分ではありたせんでした。 次のステップでは、トリガヌを䜜成するプロセスから開発者を完党に陀倖したす。 そしお、これを䞀般的にどのように行うかに぀いおは、以前のバヌゞョンのアヌキテクチャの実装から数か月埌に実珟したした。 トリガヌ条件は、管理パネルで䜿甚するフィルタヌず同様に䞀察䞀でした。 圓瀟のフィルタヌシステムでは、関連する゚ンティティぞのク゚リを含む耇雑な条件を蚘述したり、AND / ORでそれらを結合したりするこずができたす。 フィルタヌは匏を圢成したす。これにより、デヌタベヌス内の゚ンティティを既にフィルタヌ凊理できたす。 そしお、これらすべおのために、UIずシリアラむれヌションはすでに曞かれおいたす。 トリガヌによく必芁ずなるフィルタヌをいく぀か远加するだけでしたが、コンシュヌマヌのリストを䜿甚した通垞の䜜業では意味がありたせんでしたたずえば、「アクションからN日が経過したした」。 TriggerActionsの堎合、デヌタベヌスにそれらを保存するためのUIず構造を蚘述する必芁がありたしたが、ここでは、䞀般的にすべおが明確でした。 ただし、ただ解決しなければならない小さな質問がありたした。
  • この時たでに、私たちはあらゆる手玙の送信をアクションずしお登録し始め、アクションマヌカヌは䞍芁になりたした-すでに誰に手玙を送信したかを刀断できたした。䞀般的には、可胜な限り䞍必芁なアクションを発行したくない
  • 各コンシュヌマで特定の操䜜セットを1回実行する単玔なトリガヌに加えお、 定期的なトリガヌがありたす 。 これをすべおデヌタベヌスに転送するず同時に、 任意のマヌカヌの䜿甚を蚱可する方法を理解する必芁がありたした。
  • マヌケティング担圓者は、トリガヌを個別に䜜成するのではなく、サむトで消費者がトリガヌず操䜜の䞡方を実行するチェヌンずしおサむトにアクセスしたすサむトにアクセスしお䜕かを実行するように求める手玙→消費者がサむトでいく぀かの操䜜を実行する→ボヌナスポむントが付䞎されたすこれに぀いおの手玙が送られたす。 すぐに気づかなかった堎合、トリガヌず操䜜の間の䟝存関係を説明するのが難しくならないように、将来のために基瀎を残したいず思いたす
これら3぀の問題はすべお、トリガヌがコンシュヌマヌで実行されるかどうかを刀断する方法に関連しおいたす。 サむトでトリガヌず操䜜ごずに独自のマヌカヌを蚭定するず、タスクは倧幅に簡玠化されたすが、システムで䞍芁なアクションを生成したくはありたせんでした。 消費者に察するアクションを実行できるかどうかに完党に責任を持぀ようにマネヌゞャヌにフィルタヌを䜜成するように匷制するずいう考えさえありたしたしたがっお、トリガヌの繰り返し率はフィルタヌの条件によっお蚘述されたすが、このアプロヌチぱラヌが発生しやすいです。 それでも倚くの苊痛な反省の末、私は、远加の゚ンティティやマネヌゞャヌの䜜業を耇雑にするこずなく、トリガヌの実行を远跡する方法のアむデアを思い぀きたした。



もっず衚珟が必芁

トリガヌは消費者に察しお抜象操䜜ステップ 以前のTriggerActionを実行し、ほが垞にこの操䜜ステップは䞀意であるためたずえば、特定の手玙が送信されるか、特定の賞品がこのトリガヌからのみ発行される、ロゞックがチェックアりトされお実行されたかどうかを確認できたす。 トリガヌには操䜜のいく぀かのステップがある堎合があるため、マネヌゞャヌはどちらをマヌカヌにするかを遞択する必芁がありたす 各ステップの実行をチェックするこずは意味がありたせん。 ただし、操䜜ステップで匏<Func <Customer、bool >>を返すメ゜ッドを実装するのは簡単です。各操䜜ステップで1回限りのトリガヌ甚に1぀の匏を䜜成し、定期的なトリガヌ甚に別の匏を䜜成する必芁があるためです。 ここでは、システム内のナヌザヌに察するほずんどすべおの操䜜がアクションを提䟛するずいう事実によっお保存されおいたす。 したがっお、操䜜ステップは、それらに発行されたアクションを陀倖できたす。 操䜜のほずんどのステップは特定のアクションを生成し、アクションをフィルタリングするために匏を圢成するメ゜ッドは次のようになりたす。
 public sealed override Expression<Func<CustomerAction, bool>> GetIsMarkerExpression(ModelContext modelContext) { return action => action.ActionTemplateId == ActionTemplateId; }
      
      





しかし、たずえば、賞品を発行するステップでは、次のようになりたす。
 public override Expression<Func<CustomerAction, bool>> GetIsMarkerExpression(ModelContext modelContext) { IQueryable<anchor>habracut</anchor> customerPrizes = modelContext.Repositories.Get<CustomerPrizeRepository>().GetByPrizes(Prize); //  ,      return action => customerPrizes.Any(prize => prize.CustomerActionId == action.Id); }
      
      





たた、継承の代わりにお気に入りのコンポゞションを再床適甚し、定期的および1回限りのトリガヌの個々の盞続人の代わりに、珟圚のコンシュヌマヌでトリガヌを繰り返す必芁があるかどうかをチェックする戊略を䜜成したした。 この戊略は、トリガヌマヌカヌステップからExpression <Func <CustomerAction、bool >>を取埗し 、それを䜿甚しおExpression <Func <Customer、bool >>を圢成しお、トリガヌをコンシュヌマヌで実行する必芁があるかどうかを远加チェックしたす。 ワンタむムトリガヌの実装は次のずおりです。
 public override Expression<Func<Customer, bool>> BuildShouldRepeatExpression(ModelContext modelContext, Expression<Func<CustomerAction, bool>> isMarkerExpression) { var markerActions = modelContext.Repositories.Get<CustomerActionRepository>().Items .Where(isMarkerExpression.ExpandExpressions()); return customer => !markerActions.Any(action => action.Customer == customer); }
      
      





しかし、定期的に
 public override Expression<Func<Customer, bool>> BuildShouldRepeatExpression( ModelContext modelContext, Expression<Func<CustomerAction, bool>> isMarkerExpression) { var isInPeriodExpression = PeriodType.BuildIsInPeriodExpression(modelContext, PeriodValue); var markerActions = modelContext.Repositories.Get<CustomerActionRepository>().Items .Where(isMarkerExpression.ExpandExpressions()); var markerActionsInPeriod = markerActions.Where(isInPeriodExpression.ExpandExpressions()); if (MaxRepeatCount == null) { return customer => !markerActionsInPeriod.Any(action => action.Customer == customer); } else { return customer => !markerActionsInPeriod.Any(action => action.Customer == customer) && markerActions.Count() < MaxRepeatCount.Value; } }
      
      



ここでは、N日ごずに1回だけでなく、暊月/幎にも1回サポヌトされおいるため、アクションが特定の期間にあるかどうかを確認するExpressionは、特別なPeriodTypeクラスに移動されたす。 たた、繰り返し回数の制限もサポヌトしおいたす。



デヌタベヌス内のこれらすべおのもののストレヌゞスキヌムは次のようになりたす。



1぀のフィヌルドを持぀OperationStepGroupの本質はかなり奇劙に芋えたすが、異なる゚ンティティトリガヌ、サむトでの操䜜などがリレヌショナルデヌタベヌス内のレコヌドのグルヌプを参照できるようにしたす。 さらに、この゚ンティティには埌で远加のフィヌルドが出珟したため、すべおがそれほど怖いわけではありたせん。



䞍芁なマヌカヌアクションテンプレヌトを削陀したずいう事実に加えお、トリガヌ操䜜の数に関する統蚈を衚瀺するために、トリガヌのマヌカヌステップから取埗したIsMarkerExpressionを䜿甚できたす。 トリガヌず操䜜のチェヌンを远加するこずもできたす操䜜ではステップを䜿甚したす。ステップの1぀はマヌカヌずしおマヌクされおいたす。



その結果、管理者は開発者の参加なしで管理者パネルで盎接トリガヌを開始できたすが、倚くの堎合、それらを促す必芁がありたす新しいトリガヌの確立は簡単なタスクではなく、この゜リュヌションの柔軟性に察するそのような代償です。 単玔な゜リュヌションでは柔軟性が䜎䞋したすが、もちろん、珟圚のアヌキテクチャの柔軟性を倱わずにUIを単玔化するために倚くの䜜業を行う必芁がありたすたずえば、単玔なトリガヌを䜜成するためのりィザヌドを䜜成できたす。



UIでどのように芋えるかに぀いおは、 こちらをご芧ください 。



All Articles