如何改进智能联系人设计以区分同一域对象的数据及其操作功能?



我们在重新部署合约时遇到问题。每次在新合约版本部署期间更改某些逻辑时,我们都会丢失所有与合约相关的数据(存储在数组、映射中)。然后,我们需要执行数据加载过程,以便将环境恢复到所需状态,这是耗时的操作。我试图将合约拆分为拖曳合约(AbcDataContract,AbcActionsContract),但面临访问映射的问题:错误:索引表达式必须是类型,映射或数组(是函数(字节32)查看外部返回(uint256))初始合约:

contract AbcContract {
EntityA[] public entities;
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;
/* Events */
event Event1(uint id);
event Event2(uint id);
/* Structures */
struct EntityA {
string field1;
string field2;
bool field3;
uint field4;
Status field5;
}
enum Status {PROPOSED, VOTED, CONFIRMED}
function function1(...) returns (...)
function function2(...) returns (...)
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)

}

重构合约:

contract AbcDataContract {
EntityA[] public items;
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;
/* Events */
event Event1(uint id);
event Event2(uint id);
/* Structures */
struct EntityA {
string field1;
string field2;
bool field3;
uint field4;
Status proposalStatus;
}
enum Status {PROPOSED, VOTED, CONFIRMED}

}

contract AbcActionsContract {
AbcDataContract abcDataContract;
/* constructor */
function AbcActionsContract(address _AbcDataContract) {
abcDataContract = AbcDataContract(_AbcDataContract);
}
/* accessing to the mapping like abcDataContract.mapping1[someId] will raise Solidity compile error  */
function function1(...) returns (...)
/* accessing to the mapping like abcDataContract.mapping2[someId] will raise Solidity compile error  */
function function2(...) returns (...)
/* accessing to the mapping like abcDataContract.mapping3[someId] will raise Solidity compile error  */
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)

}

我们希望实现像在数据库开发中那样的方法,当存储过程/视图/其他非数据对象中的逻辑更改通常不会影响数据本身时。这个问题的最佳设计解决方案是什么?

你问题的第一部分相当容易。要访问另一个合约中的公共映射,只需使用 ():

abcDataContract.mapping1(someId)

当然,您也可以提供自己的访问方法来AbcDataContract,而不是使用公共映射。如果你沿着这条路走下去,我建议通过一个接口来访问你的数据合约。

至于你问题的设计部分,看起来你走在正确的轨道上。将数据存储分离到其自己的协定中具有巨大的优势。它不仅更容易部署,因为您不必担心迁移数据,而且部署新合同也便宜得多。

话虽如此,我想通过您发布的重构版本指出几件事。

  1. 很难说你打算用Struct1做什么。您的伪代码中没有引用它。您无法从 Solidity 中的函数返回结构,除非它们是internal调用(或者您显式分解结构)。
  2. 同样,不能在合约之间返回字符串。如果您打算在AbcActionsContract中使用Struct1.field1/2,则需要将它们转换为bytes32
  3. 您可能希望将事件定义移动到业务逻辑协定中。

将数据存储与业务逻辑分离是升级协定的关键组件。使用接口和库有助于实现此目的。有几篇博客文章可以解决这个问题。我个人建议从这个开始,然后在这里跟进。

以下是合约的近似设计,它应该解决由于部署而导致数据丢失的问题 合约业务逻辑中的一些变化:

contract DomainObjectDataContract {
struct DomainObject {
string field1;
string field2;
bool field3;
uint field4;
Status field5;
}
enum Status {PROPOSED, VOTED, CONFIRMED}
//primitives
//getters/setters for primitives
/arrays
DomainObject[] public entities;
//getters(element by id)/setters(via push function)/counting functions
mapping (bytes32 => uint) public mapping1;
mapping (bytes32 => uint[]) public mapping2;
mapping (bytes32 => uint[]) public mapping3;
//getters(element by id/ids)/setters(depends from the mapping structure)/counting functions
}
contract DomainObjectActionsContract {
DomainObjectDataContract domainObjectDataContract;
/*constructor*/
function DomainObjectActionsContract(address _DomainObjectDataContract) {
domainObjectDataContract = DomainObjectDataContract(_DomainObjectDataContract);
}
/* functions which contain business logic and access/change data via domainObjectDataContract.* Redeploying of this contract will not affect data*/
function function1(...) returns (...)
function function2(...) returns (...)
function function3(...) returns (...)
function function4(...) returns (...)
function function5(...) returns (...)
}

悬而未决的设计问题之一是应用程序分页功能。假设我们有以下结构:

struct EntityA {
string lessThen32ByteString1;
string moreThen32ByteString1;
string lessThen32ByteString2;
string moreThen32ByteString3;
bool flag;
uint var1;
uint var2;
uint var3;
uint var4;
ProposalStatus proposalStatus;
}
// 100K entities 
EntityA[] public items;

我们需要根据每个合约的函数调用对 UI 的偏移量和限制来返回数据子集。由于 Solidity 的限制/错误不同,我们当前的函数(带有用于字符串到 byte32 转换的辅助函数,将字符串拆分为几个 byte32 部分等)如下所示:

function getChunkOfPart1EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bytes32[100] lessThen32ByteString1Arr, bytes32[100] moreThen32ByteString1PrefixArr, bytes32[100] moreThen32ByteString1SuffixArr) {
}
function getChunkOfPart2EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bytes32[100] lessThen32ByteString2Arr, bytes32[100] moreThen32ByteString2PrefixArr, bytes32[100] moreThen32ByteString2SuffixArr) {
}
function getChunkOfPart3EntityADetails(uint filterAsUint, uint offset, uint limit) public constant
returns (bool[100] flagArr, uint[100] var1Arr, uint[100] var2Arr, uint[100] var3Arr, uint[100] var4Arr, ProposalStatus[100] proposalStatusArr,) {
}

从设计的角度来看,它们看起来肯定很糟糕,但我们仍然没有更好的分页解决方案。我什至不是说没有任何查询语言支持,即使是按某些字段进行的基本过滤也需要手动实现。

最新更新