MobX + 领域模型在区块链项目上的实践
我爱台南我爱台南~
网络请求库-KoAJAX
RESTful API with Token-based
export const service = new HTTPClient({
baseURI: process.env.SERVER_URL,
responseType: 'json'
}).use(async ({ request }, next) => {
request.headers = {
['Authorization']: `Bearer ${localStorage.token || ''}`,
'X-Network': localStorage.chainid || CHAINS[1].id + '',
...request.headers
};
try {
await next();
} catch (error) {
if (error instanceof HTTPError && !error.message && error.body) {
const { name, message } = error.body as Record<
'name' | 'message',
string
>;
error.message = message || name;
}
throw error;
}
});
Request Example
/**
* graph user vote detail
* @param proposal proposal id
* @param userAddress voter address
* @returns user vote detail
*/
@logData
@toggle('loading')
async getUserVote(userAddress: string) {
const { body } = await service.get<VoteCentral>(
`proposal/${this.currentPoll}/vote/${userAddress}`
);
return (this.current = body);
}
MobX-RESTful
API 层 Store 层解耦
@toggle('uploading')
async updateOne(data: Partial<NewData<D>>, id?: IDType) {
const { body } = await (id
? this.client.patch<D>(`${this.baseURI}/${id}`, data)
: this.client.post<D>(this.baseURI, data));
return (this.currentOne = body);
}
@toggle('downloading')
async getOne(id: IDType) {
const { body } = await this.client.get<D>(`${this.baseURI}/${id}`);
return (this.currentOne = body);
}
@toggle('uploading')
async deleteOne(id: IDType) {
await this.client.delete(`${this.baseURI}/${id}`);
if (this.currentOne[this.indexKey] === id) this.clearCurrent();
}
//
readAccessor = await KorisAccessor.create({
chainId,
apiUrl: formatApiUrl,
config
}));
/**
* query user joined current dac
* @param eth_address user address
*/
@action
@logData
@toggle('loading')
async setIsJoin(eth_address: string, addr = this.currentDacAddr) {
return (this.isJoin = await chain.readAccessor.contracts
.DAC(addr)
.isMember(eth_address));
}
Request example
Simple list
export class RepositoryModel<
D extends Repository = Repository,
F extends Filter<D> = Filter<D>
> extends ListModel<D, F> {
client = client;
baseURI = 'orgs/idea2app/repos';
async loadPage(page: number, per_page: number) {
const { body } = await this.client.get<D[]>(
`${this.baseURI}?${buildURLData({ page, per_page })}`
);
const [_, organization] = this.baseURI.split('/');
const {
body: { public_repos }
} = await this.client.get<Organization>(`orgs/${organization}`);
return { pageData: body, totalCount: public_repos };
}
}
Preload List
export class PreloadRepositoryModel extends Buffer<Repository>(
RepositoryModel
) {
client = client;
baseURI = 'orgs/idea2app/repos';
loadPage = RepositoryModel.prototype.loadPage;
}
Multiple Source List
export class MultipleRepository extends Stream<Repository>(RepositoryModel) {
client = client;
async *getOrgRepos() {
const {
body: { public_repos }
} = await this.client.get<Organization>('orgs/idea2app');
this.totalCount = public_repos;
for (let i = 1; ; i++) {
const { body } = await this.client.get<Repository[]>(
'orgs/idea2app/repos?page=' + i
);
if (!body[0]) break;
yield* body;
}
}
async *getUserRepos() {
const {
body: { public_repos }
} = await this.client.get<User>('users/TechQuery');
this.totalCount = public_repos;
for (let i = 1; ; i++) {
const { body } = await this.client.get<Repository[]>(
'users/TechQuery/repos?page=' + i
);
if (!body[0]) break;
yield* body;
}
}
openStream() {
return mergeStream(
this.getOrgRepos.bind(this),
this.getUserRepos.bind(this)
);
}
}
Store 设计
按照领域模型设计 Store,业务逻辑抽象为针对 Store 的增删改查
复杂 Store 分层处理
全局 root store(user)
负责 session
页面 Page Store (DAO Frame)
@logData
@toggle('loading')
async getOne(daoId: string) {
const {
addr,
name,
desc,
amount,
members,
logo,
admin,
admins,
threshold,
feeType,
feeAmount,
creator,
memberHasBadge
} = await this.getdaoHelperBasic(daoId);
this.adminOf(daoId, addr, admin, +threshold);
this.memberOf(addr, daoId, admin);
this.informationOf(addr);
return this.current;
}
模块 Module Store (proposal)
informationOf(daoAddress: string) {
return (this.currentInformation = new InformationStore(daoAddress));
}
inviteOf(daoId: string, daoAddress: string) {
return (this.currentInvite = new InviteStore(daoId, daoAddress));
}
operationOf(daoId: string, daoAddress: string, safeAddr: string) {
return (this.currentOperation = new OperationStore(
daoId,
daoAddress,
safeAddr
));
}
proposalOf(daoId: string, daoAddress?: string) {
return (this.currentProposal = new ProposalStore(daoId, daoAddress));
}