Many design decisions about how to structure and organize an IC project should be made before the development stage. On the IC, there are a few design decisions that developers should pay particular attention to.
One of the first decisions should be made is: whether an app should be encapsulated in a single canister or consist of multiple canisters.
For example, if we are going to write a simple service with no front-end, we can use a single canister to simplify project management and maintenance and focus on adding features. If our app has both front-end assets and back-end business logic, we are likely to make it consist of at least two canisters, with one canister for managing user interface components and another canister for the back-end services the application provides.
In planning, we might also consider placing some common reusable services in their own canister so that they can be imported and called from other more-specialized canisters or made available for other developers to use. The LinkUp sample dapp (linked in the references) illustrates this approach by splitting the professional service dapp into two canisters.
In the LinkedUp example, the functions that establish social connections are defined in the
connectdcanister and separate from the functions used to set up professional profiles that are defined in the
linkedupcanister. It is easy to imagine extending the dapp with a third canister, for example to schedule events based on profile attributes or shared connections.
Another common practice is to place the code for the main actor in one file with two other separate files for defining the types and utility functions.
For example, we might set up the back-end logic for our dapp to consist of the following files:
lib.rswith the functions that require an actor to send query and update calls.
util.rswith helper functions that can be imported for the actor to use.
types.rswith all of the data type definitions for the dapp.
For an IC canister, There are only two types of calls:
As a developer, it is important to recognize this relationship between the calls that query the canister and the calls that change the canister state. Query calls return results faster than update calls. Therefore, explicitly marking a function as a
query is an effective strategy for improving application performance.
Furthermore, not all queries need to go through consensus. we should also consider the security and performance trade-off that queries don’t go through consensus and do not appear on the blockchain. For some dapps, that trade-off might be appropriate. For example, if we are developing a blog platform, retrieving articles matching a tag probably don’t warrant going through consensus to ensure that a majority of nodes agree on the results. However, retrieving sensitive information—like financial data—might needs more assurance about the results than a basic query provides.
As an alternative to basic queries, the Internet Computer also supports certified queries. Certified queries enable us to receive authenticated responses that end users can trust. Using certified queries is an advanced technique that is not covered in the tutorials or other developer-focused documentation, but we can learn about how the authentication works and what we need to do to configure your program to return certified data in response to queries in the Interface specification link in the references.
The Internet Computer enables you to use stable memory to handle long-term data storage—often referred to as orthogonal persistence—and to use query calls to retrieve your data. Efficiently retrieving data using one or more keys can typically be achieved by using data structures like hash tables. It is also possible to implement a more traditional database inside a canister.
Some information in this note come from the following external links: