2

I've been trying to understand this new kind of architecture which names can be Onion architecture, Clean architecture, Ports and Adapters, etc.

If I take the abstraction of Ports and Adapters, when I adapt my application for a particular port, is it ok for me to give the port an entity from inside my application? Or am I always supposed to adapt the entities also, to fit the port?

Example:

Say I have a Customer entity. I have a UI that uses my application. My UI calls through an Adapter to getCustomerById(123). In turn, my adapter will call through to my application, effectively retrieving a Customer using the injected Repository, and it will perform some sort of formatting on it and logging and what not, once the Customer is ready, it is returned to my UI. My question here is, my Customer object is returned as is to my UI. This means my UI has a reference to the Customer class from my Core project. My UI then goes on using that Customer object to do things, maybe change it's name, etc. and eventually calls the adapter again to updateCustomer(customer).

Is this ok to do? Is it ok that my UI uses the Customer class from inside my application core. Or should I instead adapt my Customer to a new Customer object say UICustomer and have my UI work with that instead, mapping back and forth between Customer and UICustomer at the adapter level?

Didier A.
  • 3,650
  • 2
  • 35
  • 38

2 Answers2

1

I too am starting to learn/implement the onion architecture. From what I can tell, your original method (Customer Entity in the UI) is an acceptable practice. Look here for a graphical, linear, representation of the Onion Architecture:

http://jeffreypalermo.com/blog/the-onion-architecture-part-3/

You can see that the UI can interact directly with an underlying Object Model. Since your customer is not a direct DB entity, but instead loaded from an injected repository, this seems to fit the model of the Onion.

My assumption here is that the Customer Entity is really a composite of different, normalized, DB entities which are assembled via the injected repositories.

My understanding is that in principle, the interactions are done through abstractions, and the fact that the Customer Entity is an abstracted representation of the Customer Database, this is valid.

Please, correct me if any of the above assumptions/thoughts are incorrect.

jgriffin
  • 176
  • 1
  • 11
  • My issue is, if I do this, I create a dependency from my UI to my entity. Now, say I am working on the core of my onion, refining my domain model, adding new behaviours on them, refactoring the properties of my entities, maybe splitting name into first and last for example and doing a bunch of other changes that are good in terms of my core. These changes might break the UI, because the entity might have changed to much for it. I will need to go and update my UI to use the new entity instead. – Didier A. Feb 21 '14 at 15:09
  • The second issue I have is, maybe my UI will somehow suddenly require a special need on my entity. Say it must uppercase the Name field because of formatting constraint that are unique to my UI. Will I go and add an UpperCaseName field on my entity? If I do that, I'm leaking the UI into the core. So both this issue and the one above have the impact that I might refrain from changing the entity because it will break the UI, or I might change the entity because of requirements only needed by the UI. – Didier A. Feb 21 '14 at 15:09
  • My Repository is abstracted because in my core, it is only an interface, but outside, it becomes an implementation. My entities though, are not abstracted, because in my core, they are implementations of data and behaviour. If I re-implemented the entity on the outside, by making a Customer that is specific to my UI requirements and mapping my core Customer to my UI customer, it would be more akin to what is being done with the Repository. It has the downside that, in most cases, both Customer objects might actually be identical. So I'm not sure it's worth the effort and time. – Didier A. Feb 21 '14 at 15:20
  • I too am struggling with the same questions. I am a data first application developer. For me, it would make more sense to have the UI perform any 'prettying' of the data. In your example, changing the case of the string returned should reside in the UI portions. As for the separating the name into first and last, that is a database change, and as such, any change to the data would require a change upstream to the UI, or atleast providing an adaptor that puts those fields together. I just do not see a way around that. – jgriffin Feb 24 '14 at 14:09
1

Great question. I have an example that might be useful. https://bitbucket.org/jeffreypalermo/onion-architecture

For simple applications using the Core domain model objects can be just fine. These are designed to not have nasty tentacles of dependencies hanging off of them, so they work very well and are quite portable. They can travel across the layers without causing any issues.

culix
  • 8,979
  • 5
  • 31
  • 49
Jeffrey Palermo
  • 414
  • 4
  • 4
  • Thank you for sharing this repo. It actuall answered a bunch of questions for me, but I still have one big one. Where would you put the business logic/data validity checks? I am assuming it would be in the VisitorProcessor. Am I correct? – jgriffin Feb 24 '14 at 14:32
  • I see in your example that the domain model is anemic. It looks like you're putting the domain logic in your Processor class, but I'm not sure if this is on purpose or it's that your domain model is just too simple and does not require any logic and that your Processor class is your Service layer. This is kind of what my problem is, if I have an anemic domain model, and therefore, a simple one with no behaviour, I understand that it's more easily shareable with the UI, but if I have a complex domain model with a lot of behaviour and deeply nested relationships, should I still use it in my UI? – Didier A. Feb 24 '14 at 19:43
  • @jgriffin, Data validity can be separated into two types. 1) data type, 2) business suitability for an operation. Define your model with strict typing to ensure that only data that meets the type can get in. For instance, us ushort instead of int if you don't want negative numbers or don't want large ones. Then use external business logic or rules patterns to check to see if the model is "valid" for a certain operation or command to be invoked. – Jeffrey Palermo Feb 24 '14 at 21:34
  • @didibus, This sample is very simplistic. Logic that belongs in a domain model object is logic that operates only on data owned by the object or objects that are passed into the method. It's typically a bad idea to "reach out" to another class or dependency in order to pull behavior into a domain object. – Jeffrey Palermo Feb 24 '14 at 21:35
  • @JeffreyPalermo Right now I understand that my domain objects should only depend on other domain objects, nothing else. Their logic must only affect their own data or data of other domain objects they own or are passed as arguments. Are you saying that by doing this, the domain will therefore be very portable and thus ok to use in the UI? And I'm guessing only special requirements might suggest I need to map them to something else, say, serializing them over a service for example. Is this correct? – Didier A. Feb 25 '14 at 20:25
  • @didibus, You are right about portability. I like to make it easy. If the domain objects (model) resides in the "Core" project, which only has references to System.dll and System.Core.dll, then we guarantee these classes have no other dependencies that would hinder their portability. – Jeffrey Palermo Feb 26 '14 at 13:45
  • @didibus, An example of a special case or special requirements would be a web application with lots of data on the screen presented in a way specific to the screen. Instead of leaving this mapping responsibility to the view, you can pull it further back and map it to a presentation object that aligns with the structure of the screen. – Jeffrey Palermo Feb 26 '14 at 13:47
  • @JeffreyPalermo What exactly are the relevant parts of your linked example that answer this question? This answer would be much more useful if you could summarize what you mean in a sentence or two, or post the short code example that you feel best answers this. – culix Dec 01 '14 at 04:54