Our team has a project which is basically a MIS. The architecture we are using is CQRS with DDD(Domain-Driven Design). We have persistence objects in persistence layer, domain objects in domain layer, data transfer objects to carry input information from a user, and view objects to show data on particular page.
I do think this kind of design is great. But during implementing the project, we found some problems that blocked us a lot. The most disgusting thing is we have to write many converters to convert objects between two different layers, like PO to DO, DO to PO, DTO to DO. There are too many get and set statements in these converter. The reason why we didn't use things like BeanUtils is that it does not work well when fields in two objects have different types or names. Obviously these codes break Open-Close-Principle. Whenever there is a change in a particular page or we want to change a field in DB, it would be a nightmare.
I wonder is it really necessarily separate DO and Po and can we just simplify the architecture and design to make them same thing because in most of cases they just contains same fields with little difference. How can we simplify the design to avoid the problems we are facing and improve our productivity and assure extendability and stability of the software?
If you look at DDD tactical patterns you will find nothing about "domain objects". You have aggregates, that consist of entities and one of those entities is your aggregate root. You might only have one entity per bounded context, which would be your aggregate root.
You persist your aggregates as a whole. Basic repositories operate in "collection style", allowing you only to put a new aggregate to the repository and retrieve one aggregate from the repository by its identity.
CQRS splits one object into two. One object is your aggregate optimised for writing. Usually, it is enough to have a collection-style repository to be used in your command handlers on the "write side". On the "read side", however, you have flatter read models, that represent what you want to show to your users in a way that is optimised for reading.
If you say you use CQRS but have only one model and one database, this does not have a lot of sense. If you have command handlers, it does not mean you use CQRS.
After this clarification, we can look at aggregate persistence. No one ever said that you cannot persist aggregates as-is. In a way this is actually the preferred method, if you have a proper storage. Usually, ORM is not a proper storage because it creates friction in places where you find impedance mismatch between object world and relational database world. Document databases are better suited for this. PostgreSQL JSONB feature is a good candidate too.
The flow would then be:
As you can see, there is no real place for any persistence "conversions" here. If you have a proper storage, it is all good and fine.
When you read, however, you have a request coming from the client. This request is then goes to some query provider, data provider or whatever you call this. Some put all these queries into their repositories but this only works if you are not using CQRS or at least have one persistence model, not two.
Queries are idempotent, they do not change the state of your system and can run as many times as you like without consequences except your persistence layer may be loaded if reads are not optimised. However, there is no need to put lots of abstractions on the read side. At the same time, you should not send aggregates back to the client that queries your domain. You need to have a DTO that represent the client's view model for a particular need, not less and not more. This DTO needs to be optimised to be rendered on the client side as fast as possible, with no calculations and conversions. This is basically what CQRS suggest to do on your read side - prepare your data to deliver these DTOs fast.