订阅管理逻辑

eoi*_*noc 3 design-patterns user-management subscription e-commerce recurring-billing

对于用户可以是member或的系统admin,具有该member角色的用户必须使用定期订阅付费,或者获得免费访问权限.

我目前的做法:

  • 用户有一个user数据库表.
  • 一个subscription表包括针对用户的记录,如果他们有一个订阅.
  • 一个subscription_event表记录每一个计费或失败的付款.我可以查询这个以查看最后一个事件是否确实是一个成功的付款.

但是,如果用户获得"免费"访问,我应该如何记录?

  • 有另一个complimentary_subscription用户ID作为外键的表吗?
  • 为他们录制一个特殊的"订阅" subscription
  • 或者在列的用户行中添加另一列,例如is_complimentarycomplimentary_expires_date
  • expires在用户行中添加更通用的列?

nic*_*083 7

问题回顾

正如@leanne所说,你正在建模一个人Subscription的专业化,比如说,MonthlySubscriptionComplimentarySubscription给他们一个这个答案的名字.

您知道订阅​​可能会过期:

  • 对于a MonthlySubscription,当用户没有支付当前月份的订阅时会发生这种情况
  • 对于a ComplimentarySubscription,到期日期在分配给用户时分配

正如你所看到的ExpirationDate,任何一个都是必不可少的属性Subscription,但你存储它的方式在每种情况下都是不同的.如果是第一种情况,你必须根据最后一个事件计算它,在后者中你可以直接检索它.

处理数据库中的继承

因此,要将此示例模型映射到数据库模式,您可以使用Martin Fowler的"企业应用程序架构模式"一书中描述的类表继承模式.这是它的意图:

"表示类的继承层次结构,每个类有一个表".

基本上,您将拥有一个包含类之间共享属性的表,并且您将在单独的表中存储特定于每个类的属性.

记住这一点,让我们回顾一下您提出的选项:

  • 有另一个complimentary_subscription用户ID作为外键的表吗?

有一个单独的表来存储ComplimentarySubscription特定的细节听起来不错,但如果你没有把这个与subscription表格联系起来,你最终会得到一个既有a MonthlySubscription又有a 的用户ComplimentarySubscription.它的外键应指向subscription表,该表告诉您用户是否有订阅(并且您必须为每个用户强制执行一个订阅).

  • 为他们录制一个特殊的"订阅" subscription

是的,您必须记录用户每月订阅或免费订阅.但是,如果您正在考虑记录数量为零的特殊订阅,那么您正在寻找与您当前设计相匹配的解决方案,而不是为其搜索正确的模型(它可能也可能不是).

  • 或者在列的用户行中添加另一列,例如is_complimentarycomplimentary_expires_date

我个人不喜欢这个,因为你把信息放在它不属于的地方.考虑到这一点,您将在哪里存储免费订阅的到期日期(请记住,对于您正在计算它的月度,并且不存储到期日期)?似乎所有这些信息都在为自己的"家"而哭泣.此外,如果稍后您需要添加新类型的订阅,该表将开始混乱.

  • expires在用户行中添加更通用的列?

如果这样做,每次subscription_event更改时都必须处理数据同步(在每月订阅的情况下).通常我会尽量避免这种数据重复的情况.

样品溶液

在添加新类型的订阅时,我要做的是支持可扩展性,即让subscription表格在MonthlySubscripton和之间存储共享详细信息ComplimentarySubscription,添加一个type列密钥,使您可以区分与行相关的订阅类型.

然后,在其自己的表中存储特定于每个订阅类型的详细信息,引用父subscription行.

对于检索数据,您需要一个负责实例化Subscription给定行的type列值的正确类型的对象subscription.

您可以查看"模式企业应用程序体系结构"一书中的模式,以获得有关如何定义type列值,如何使用映射器对象进行Subscription实例化等方面的进一步帮助.


01/03/2012更新:定义和处理type列的替代方法

这是一个更新,以澄清@enoinoc在评论中发布的以下问题:

特别是对于建议的type列,这可能是指向Plans描述不同类型订阅的表的外键,例如在没有付款的情况下到期之前的几个月.这听起来合乎逻辑吗?

Plans只要不是不需要编辑的静态信息,就可以在表中包含这些信息.如果是这种情况,请不要过度概括您的解决方案并将该知识放在相应的Subscription子类中.

关于外键方法,我可以想到这样的一些缺点:

  • 请记住,您的目标是知道Subscription要为Plans表中的每一行使用的子类.如果你得到的只是一个外键值(比如一个整数),你就必须编写代码来将该值映射到要使用的类.这意味着额外的工作:)
  • 如果你不得不做额外的不必要的工作,那么主要可能就是痛苦:每次添加新计划时,你都必须记住在映射代码中硬编码它的外键值.
  • 如果数据库导出/导入操作不正确,外键可能会更改.如果发生这种情况,您的映射代码将不再起作用,您将不得不再次部署您的软件(或者至少部署更改的部分).

提出的解决方案

我要做的是放入表格中的typePlans.该列将保存知道如何Subscription从特定行构建权限的类的名称.

但是:为什么我们需要一个对象来构建每种类型的Subscription?因为您将使用来自不同表(subscription_eventcomplimentary_subscription)的信息来构建每种类型的对象,所以隔离和封装该行为总是好的.

让我们看一下Plans表格的外观:

- 计划表 -

Id | 名称| 输入| 其他栏目......

1 | 每月| MonthlySubscriptionMapper |

2 | 免费| ComplimentarySubscriptionMapper|

每个都SubscriptionMapper可以定义一个Subscription MapFrom(Row aRow)从数据库中获取一行的方法,并为您提供Subscription子类的正确实例(MonthlySubscriptionComplimentarySubscription在示例中).

最后,为了获得type列中指定的映射器的实例(不使用讨厌的ifcase语句),您可以从列的值中获取类名,并使用反射创建该类的实例.