如何为已审核发票分配连续递增的参考编号?
发票审核通过时分配连续编号的实现方案探讨
我在业务逻辑中遇到需求:当审核通过一张发票时,需要为其分配下一个连续编号作为参考。考虑在数据库创建仅含一行数据的InvoicesCounter表(字段:LastNumber、Timestamp),通过Timestamp实现并发控制,目前有两种思路,想咨询这两种方案是否可行,以及是否存在更优实现方式?
方案一:应用层直接处理
MyAplicationLayerMethod() { Invoice myInvoiceToAccept = _unitOfWork.InvoiceRepository.GetInvoice(1); // 修正拼写:Invoce→Invoice,GetInvoid→GetInvoice InvoiceCounter myInvoiceCounter = _unitOfWork.InvoiceCounter.GetLastNumber(); myInvoiceToAccept.Accept(myInvoiceCounter.LastNumber); // 修正:原代码myLastNumber未定义,应取计数器的LastNumber myInvoiceCounter.Increment(); _unitOfWork.Commit(); }
方案二:通过领域服务处理
MyAplicationLayerMethod() { Invoice myInvoiceToAccept = _unitOfWork.InvoiceRepository.GetInvoice(1); // 修正拼写错误 InvoiceCounter myInvoiceCounter = _unitOfWork.InvoiceCounter.GetLastNumber(); InvoiceDomainService.AcceptInvoice(myInvoiceToAccept, myInvoiceCounter); // 修正命名 _unitOfWork.Commit(); } class InvoiceDomainService { public static void AcceptInvoice(Invoice paramInvoice, InvoiceCounter paramInvoiceCounter) // 修正拼写错误 { paramInvoice.Accept(paramInvoiceCounter.LastNumber); // 补充传递编号 paramInvoiceCounter.Increment(); } }
两种方案的可行性分析
- 方案一:可行但不符合分层架构原则。将发票审核+编号递增的业务逻辑直接写在应用层,会让应用层承担领域层的职责,导致代码耦合度高,后续如果有其他场景需要生成发票编号,容易出现重复代码,维护成本上升。另外原代码存在拼写错误和未定义变量的问题,实际运行会报错。
- 方案二:更符合DDD(领域驱动设计)规范,把核心业务逻辑封装到领域服务中,应用层仅负责协调资源调用,职责划分更清晰,代码内聚性更强。但同样需要修正原代码中的拼写错误,以及补充发票审核时传递编号的逻辑。
更优实现方式推荐
1. 数据库层面强化并发控制
依赖Timestamp做乐观锁是可行的,但可以利用数据库原子操作或行锁进一步避免并发冲突:
- 原子递增语句:直接在数据库层面完成编号递增并返回新值,比如SQL Server:
这种方式能保证编号生成的原子性,避免应用层先读再写导致的并发重复问题。UPDATE InvoicesCounter SET LastNumber = LastNumber + 1, Timestamp = GETUTCDATE() OUTPUT INSERTED.LastNumber; - 行级锁:对于支持行锁的数据库(如MySQL InnoDB、PostgreSQL),读取计数器时加锁:
确保同一时间只有一个事务能修改计数器。SELECT LastNumber FROM InvoicesCounter FOR UPDATE;
2. 领域对象封装优化
让InvoiceCounter自身负责生成下一个编号,而不是外部调用Increment(),强化领域对象的封装性:
public class InvoiceCounter { public int LastNumber { get; private set; } public DateTime Timestamp { get; private set; } // 生成下一个编号并更新自身状态 public int GetNextNumber() { LastNumber++; Timestamp = DateTime.UtcNow; return LastNumber; } }
然后领域服务简化为:
public class InvoiceDomainService { public void AcceptInvoice(Invoice invoice, InvoiceCounter counter) { var nextInvoiceNumber = counter.GetNextNumber(); invoice.Accept(nextInvoiceNumber); } }
3. 拆分计数器避免单点瓶颈
如果发票量较大,单一行的InvoicesCounter可能成为性能瓶颈,可以按业务维度拆分计数器,比如按年月(InvoicesCounter_202409、InvoicesCounter_202410)、按客户类型等,不同分片的编号独立生成,提升并发处理能力。
内容的提问来源于stack exchange,提问作者Álvaro García
相关产品推荐
相关产品推荐

