hotzen
hotzen

Reputation: 2873

Domain-Classes/Entities with clean Persistence-Layer (DAOs, Factories, ...)

What is a clean way to combine Domain Objects/Entities with a persistency layer without ORM-framework support?

I have the Domain-Classes/Entities zcl_document and zcl_document_request (1:n). I want the Domain Classes to only contain core domain logic, no infrastructure, no "helpers", no persistency/loading-mechanisms.

Being in ABAP, I have defined "clean" structures zs_document and zs_docreq for each entity that are exposed as a public-readonly data attribute (we're in ABAP after all). This way I don't need a bunch of getters on the entities and minimize the methods to core domain-logic.

To get a thin persistency-layer, I have defined DAO-interfaces which read, save and find_by_x for each database table (including the optional text-table and stuff). Their return-type is always the structure or a table of structure, not the entity-object itself. So I have a testable/replaceable thin persistence-layer. This persistency-layer is now also useable in reports that do mass-data processing because I am not forced to create object-instances or graphs of objects, I can resort to work with (hopefully clean) structures.

To instantiate an Entity, commonly each Entity has a public-static create (factory) method, that takes the "clean" structure, validates and produces its instance. Entities which have fundamental associations to other objects are more difficult to create, since the dependent objects have to be created as well. Those entites get their own zcl_document_request_manager (bear the naming). The Manager knows how to create (factory) and save the entity, including all associated objects. It is therefore also a friend of the entities.

The factories are the only places that know the DAOs to keep the Entities themselves clean from infrastructure/persistency stuff. The loading is done eagerly, I don't have any idea how to create transparent lazy-loading without having too much infrastructure-management-code in the entities.

Working with it will look like the following:

create object lo_docreq_mng exporting dao, dao, dao, dao,...

lt_docreq = docreq_dao->find_by_x( ... ) // table of structure

foreach lt_docreq as ls_docreq // structure
  lo_docreq = lo_docreq_mng=>create( ls_docreq ) // factory => instance

  lo_doc = lo_docreq->get_document( ) // was created with document-instance

  lo_docreq->do_something_mutating( ).      
  lo_docreq_mng->save( lo_docreq) // save including dependent objects

Is this feasible or is there some smell? Any comment appreciated.

Upvotes: 2

Views: 397

Answers (2)

Gabor Farkas
Gabor Farkas

Reputation: 21

I know this is an old question but in my latest projects I've been trying to come up with a good solution for this same problem.

I generally agree with your approach and follow one very similar to yours except I don't define any DAO type objects. Most of the time I don't just define a manager class but two separate classes: a repository for managing database retrieval/save and a factory for creating new, valid instances. This is just in order to not violate SRP (also, I tend to define these as interfaces). This plays along nicely with writing unit tests too.

As you already mentioned the biggest issue is eagerly loading relationships which blows up very quickly once you start working with larger amounts of data. The solution is probably that the repositories should know the unique identifiers of each entity (ie. the primary key in the DB) and only get those at instantiation but I don't actually have an implementation yet.

Upvotes: 0

wintermute
wintermute

Reputation: 31

I like your approach. I would change some things:

1) to keep the user of your domain objects clean of construction code, you could provide a docreq-class which returns a list of entities instead of structures:

lt_doc = doc_qry->find_by_x( ... ) // table of entities
foreach lt_doc as lo_doc 
  lo_doc->do_something_mutating( ).      
  lo_doc_mng->save( lo_doc ) // save including dependent objects

The usage is less error-prone and clarifies the business logic in your code. And since hopefully your entites are used often, you simplify the usage for other developers.

2) Maybe you could also get rid of the save-Method. Why do you have to call save explicitly? In my experience the decision to save or discard something is placed to the controller of the running transaction (Top-level report or running UI). But it should not be placed into some kind of business or service method/function.

3) I wouldn't go for static methods. In the first place they are easy to implement, but for later changes they could become hell

and regarding the discussion about using ORM: I wouldn't mix up persistency and domain logic, since it reduces testability. The usage of ABAP-ORM solely as persistency-layer has no great benefit. Why wrap data in a class and use it like a simple abap-structure (in most cases)? On the other hand it lacks a nice DQL.

Upvotes: 2

Related Questions