Essential Design Considerations for Ethereum ÐApps (2): How to Organize your Data (Struct VS. Child Contract)
This is the second publication of a series that aim to promote using Ethereum for building DApps. You may check the first publication of this series “Upgradable Smart Contracts” before you continue because some ideas are connected.
Which is the best? One smart contract for all entities, or one smart contract deployed for every entity? Actually, for many reasons and in most cases, you should consider deploying only one contract that holds the information of all your entities that is of the same type. However, first things first, let us take the following two samples to illustrate the two possible designs which this article is addressing.
The two designs we are discussing
1st design sample: Save all members basic data in one contract utilizing a “struct”:
A sample project that follows this pattern could be found here. Which is a Marketplace smart contract to exchange health information.
2nd design sample: Save every member’s basic data in a different deployed version of a contract
Which design pattern is the best?
In the context of saving data for DApps into blockchain using smart contracts the first proposed design , that uses a struct, is superior. This is because of the following reasons:
When you save data at Contracts you are using Contracts as Tables
From design perspective Contracts that saves data are like tables not like objects. To make things clear, let us speak for example about “people” in a decentralized vehicle leasing system. You should think like if you have a database and you want to create tables. And, you should think in a way as you are creating a table that will save all people’s data. And, you will never create a table for each person. Similarly, a contract should in-mind represent a table. And you will create one Smart Contract for all people to store there information inside that Contract. You should not deploy a contract instance for each person.
Solidity is a Contract-Oriented Programming Language
You should not think about a contract as exactly an object in OOP. In contrast, as proposed by Ethereum, Solidity is a Contract-Oriented Programming Language. So, you should not just adapt all your OOP in solidity. However, some OOP concepts are applicable, like inheritance. But, some other concepts should be different; like the way we design the communication between contracts. Actually, this is the main difference I can see between Object Oriented Programming vs. Smart-Contract Oriented Programming.
Less Cost (Less Gas)
Another consideration to be added to the design perspective of Solidity, as a Smart Contract Programming Language, is the cost. If you will go with the second proposed approach, you will significantly need much more gas to operate your system. Note that the “gas” required to deploy a contract is much much more than the gas needed to communicate with another contract. Additionally, the gas needed to interact with a different contract is much much more than the gas needed to do some internal operation within the same contract.
Even for private Ethereum, less gas still means faster processing of the method call. Specially because calling another smart contract will need to perform another transaction. Whereas, calling a method will happen in the same transaction.
Less Operations when updating data
Additional consideration, that should come to mind, is the operation overhead needed to manage the contracts. If you go with the second approach, you should think about how much headache you will have if you will need, for example, to change the secondary owner of the contract; For example, in case you set a secondary owner (could be also a multi-sig contract) that is managed by your organization and not by the end user. Which will be needed, in case the end user (the member) lost his private key for example! And you are requier to have this functionality to save people data.
Less Overhead when implementing new code logic
Referring to the first article of this series, Upgradable Smart Contracts, you can see how complicated it is to maintain upgradability and deploy it when needed. To see how painful it could be to go with the wrong choice, just imagine that you have ten million customers. And let us say you will go with the second suggested design. Now each customer will have his own version of the contract specially deployed for him. What is the cost to update all those ten million, even on a private Ethereum network?
You can use multiple libraries and/or base contracts
Instead of using a different contracts and communicate with them, redesign contracts, which contain data, to be a base contract (or a library) used by derived class. In this way, the struct that contains the data and its related read, add, update and delete methods could be isolated at a different file.
So, if you are bothered from having long file of solidity, that contain many structs and their associated methods all together, just split each struct with its related code into a separate base class or library and make the main contract inherit from those base contracts. This is one of the main reasons, I think of, why solidity allows multiple inheritance.
Example for some Complex structure, a Marketplace:
Let us think about a Marketplace. You will need, for example, to save the sellers/buyers, all the products/services they sell/buy and all the transactions that happens between them.
Actually, after putting in mind the mentioned points, you may decide to organize your logic in separate classes and libraries to ensure maintainability and ease upgrading. In such a case you may decide to have contracts for each of the following entities:
- People: To store people information (because every buyer may sell something and therefore be a seller, this contract will save both Buyers & Sellers)
- Products: To store products information.
- Transactions: To log buying and selling transactions.
- Market: To maintain your logic (This smart contract could handle and process purchases)
Note that, you can save the data of People, Products and Transactions in the same Market contract. But, having one smart contract for all data and logic, as discussed in Upgradeable Smart Contracts, will complicate the future updates of your system. Therefore, to keep the design modular, I suggest to make a contract for People, another one for products and an additional one for Transactions. This is to use them as base classes to the Market smart contract. In this way People, Products and Transactions will contain data, while Market will contain your business logic.
The above points is specially required for DApps. And it is for the case when you want to save data at blockchain in solidity style of doing things. However, even thought it is less likely to happen, you may run into a situation that those are not valid reasons for using structs over child contracts. And they may not be valid in a different context and different situations. But, still the mentioned above are best fit for DApps.