Core Data Calls Them Contexts for a Reason


It seems theUnit of Work I envisioned is not the one I need. It seems Core Data calls NSManagedObjectContexta “context” for a reason. It doesn’t suffice to set one up and pass it to every data store. At least not if you perform changes on background queues and with nestedcontexts.

So the bestI could come up with was to expose the transactional context and add a completion block to schedule follow-up tasks until theend:

func feedBiggestBanana() {let eventQueue = EventQueue()unitOfWork().execute({ context inlet bananaRepo = CoreDataBananaRepository(context: context)let childRepo = CoreDataChildRepository(context: context)let child = childRepo.hungriestChild()let banana = bananaRepo.biggestBanana(), eventQueue) // enqueue AteFood event}, completion: {DomainEventPublisher.publishFromQueue(eventQueue)})}clann Child {func eat(food: Food) { ... }}

It’s not too bad to have an NSManagedObjectContextexposed to an Application Service. That’s what they are madefor.

But still…

Instead, I could pass in something like a UnitOfWorkConfigurationto the block. It’s a facade to the realrepositories.

protocol BananaRepository {func biggestBanana() -> Banana}protocol ChildRepository {func hungriestChild() -> Child}class CoreDataChildRepository: ChildRepository { ... }class CoreDataBananaRepository: BananaRepository { ... }class UnitOfWorkConfiguration {let transactionContext: NSManagedObjectContextinit(transactionContext: NSManagedObjectContext) {self.transactionContext = transactionContext}lazy var bananaRepository = CoreDataBananaRepository(context: self. transactionContext)lazy var childRepository = CoreDataChildRepository(context: self. transactionContext)}extension UnitOfWorkConfiguration: ChildRepository {func hungriestChild() -> Child {return childRepository.hungriestChild()}}extension UnitOfWorkConfiguration: BananaRepository {func biggestBanana() -> Banana {return bananaRepository.biggestBanana()}}

This is pretty boring implementation-wise. It reminds me of what we did inJava.

Even worse, though, it’s focusing on an implementation problem. The resulting types are not proper objects. The facade has only fake responsibilities. It acts as something different while its real responsibility is to make client code easier: only a single UnitOfWorkConfigurationinstance needs to be passed around instead of the real repositories. That’s not the same quality of delegation as in a Book object having many Pages objects, delegating print-outs to the pages. I’d rather have a factory object to instantiate repositories with know the transactional context and create a new factory with each unit of work than a facade to deal with the code I myselfcreated.

What’s an object supposed to do? Currently, a unit of work can be executed. It reports errors. It collaborates with the managed object context. That’s aboutit.

Can a unit of work not also prepare repositories? “Here, use that tool when you do your job!” I think that works outokay:

let uow = unitOfWork()let bananaRepository = CoreDataBananaRepository()uow.prepare(bananaRepository)let childRepository = CoreDataChildRepository()uow.prepare(childRepository)uow.execute({ context in let child = childRepository.hungriestChild() let banana = bananaRepository.biggestBanana(), eventQueue) // enqueue AteFood event}, completion: { DomainEventPublisher.publishFromQueue(eventQueue)})

Repositories (and other persistence mechanisms using NSManagedObjectContexts) can share a common protocol which the unit of work uses to set thecontext.

The other way around could work aswell:

let uow = unitOfWork()let bananaRepository = CoreDataBananaRepository(operateIn: uow)let childRepository = CoreDataChildRepository(operateIn: uow)

Here, the repositories would then obtain the current transactional context from the unit of work. I favor Tell Don’t Askwherever I can to keep the flow of information easily comprehensible, so I don’t feel too happy aboutthis.

All this boils down to is that I’d like to open up a block and create repository instances in it and have them know about the … context.That’s when it hit me why the APIdesigners may have called it NSManagedObjectContext. It’s just everywhere. You can’t easily box itaway.

This is what I nowhave:

Unit of work as sole source of truth about the currentcontext. Repositories (and similar) cannot be set up during bootstrapping but have to be created with every newtransaction. Consequently, Domain Services cannot hold on to repository instances but need to have them injected with every call. (This tradeoff is okay. Don’t fight the framework toomuch.) Also, interactors (as data sources for view presenters) cannot operate in isolation but need guidance from, say, use case Application Services which take care of the unit of worksetup.

In the end, there’s only one source of the current context, and only one layer where these come from. From that follows that repository implementations are not to be referenced strongly in attributes but should be consideredvolatile.

Still not sure about how to get the context from the unit of work into related objects. The facade is out, although it’d beconvenient.

I’ll have to see where this is leading me. Drawing some diagramsnow.