Consider the following situation.
Three apps A,B and C must cooperate together: A is an external, third party app while B and C are in-house apps (so we have control over B and C, not over A). B replies to requests made by A, using both logics contained in C and B itself. Think about B as a layer between A and C.
There are some underlying common concepts that A,B and C understand and use.
Suppose the key task here is to decouple everything, so that if tomorrow we want to use A1 instead of A, all the interactions between B and C stay fixed (and, respectively if we want to use C1 instead of C, all the interactions between B and A stay fixed).
The question is about the datamodel design for B and C. Two solutions come to my mind:
- Shared datamodel: we introduce a datamodel D in a different project. D contains the "in house" version of the common concepts and is used by B and C: an A-version of the concept is mapped to a D-version and can be used and understood by both B and C. If tomorrow we want to use A1 instead of A, we simply re-write the adapter. If instead we want to get rid of C, we write C1 using the common datamodel D.
- Replicated datamodel: both B and C have its own version of the datamodel. We now have two adapters: one between A and B and one between B and C. If tomorrow we want to change either A or C then we rewrite the respective adapter.
Is there any best practice for dealing with this situation? Is there any alternative to 1. and 2.? Is there any intrinsic problem with either 1. and 2.?
Edit Following request I'll try to give a more explicit example (of course everything here is fictional. Also forgive my horrible fantasy).
ACME ltd is a used car retail company, needing detailed information regarding every car bought and to-be resold. This process is outsourced, so they expose a simple DTO with two classes ACME.CarInfoRequest
and ACME.CarInfoResponse
(containing proper fields). In particular there is the business concept of ACME.Car
.
ACME is outsourcing to INITECH inc, a car data provider. INITECH has a big and updated database containing car info and also features a live connection to the police records to check if the car is stolen. INITECH has one main app to interface with the customer and uses a different app to communicate with the police: the INIMAIN app and the INIPOLICE app. Both apps have the underlying concept of "Car".
The question is: should INITECH use a shared datamodel and let INIMAIN and INIPOLICE add it as a dependency or should implement mirrors in the two apps? In other words the two solutions could be:
1) INITECH builds INIDATA project. INIDATA contains INIDATA.Car
to represent the concept of "car". Both INIMAIN and INIPOLICE add INIDATA as a dependency and use the same INIDATA.Car
. When INIMAIN and INIPOLICE speak about a "Car", no translation is required (as they both refers to the same INIDATA.Car
). On the other hand, INIMAIN maps the info contained in ACME.Car
into INIDATA.Car
via an adapter.
2) INIMAIN has its own representation of INIMAIN.Car
(and respectively INIPOLICE has INIPOLICE.Car
). When INIMAIN wants INIPOLICE to ask the info about a car, first translates from INIMAIN.Car
to INIPOLICE.Car
. Then when INIPOLICE replies, INIMAIN translates everything back from INIPOLICE.Car
to
INIMAIN.Car
. Of course ACME.Car
is still mapped into INIMAIN.Car
via an adapter.
Hope it's more clear now (even if probably the example is awkward, again forgive my limited fantasy).
The answer to this question depends on the list of services that INTECH plans to provide to its customers.
INIMAIN
andINIPOLICE
modules share a singleINDATA.car
domain model (since there is only one data model that needs to be supported i.e, the data model understood by the external police application thatINPOLICE
will speak to)CARINSURANCE
module that talks to external insurance data service, it would make more sense thatINPOLICE
andCARINSURANCE
modules have their own data models that are modeled as per the external services they speak to (Police record external service and insurance record external service will usually have their own request model). In this case, INTECH should haveINDATA.PoliceCar
andINDATA.InsuranceCar
data models andINMAIN
would mapACME.car
to eitherINDATA.PoliceCar
orINDATA.InsuranceCar
depending on what type of information is being requested byACME
It all boils down to whether INTECH plans to provide a single service or multiple services to its customers. If this is difficult to determine in the near future, it would make more sense to stick to a single data model shared by
INPOLICE
andINMAIN
rather than over-designing for a use-case that INTECH might never ever encounter.