版權(quán)聲明:
以下內(nèi)容來自微信公共帳號“EOS技術(shù)愛好者”,搜索“EOSTechLover”即可訂閱,翻譯Gavin,校對Lochaiching。轉(zhuǎn)載必須保留以上聲明。僅授權(quán)原文轉(zhuǎn)載。
本文原文鏈接為https://steemit.com/eos/@leordev/eos-todo-dapp-free-ram-model-and-state-management-eos-redux-3 ,由本號“EOS技術(shù)愛好者”翻譯。
"EOS技術(shù)愛好者"全程由EOShenzhen運(yùn)營,喜歡我們請為我們投票(EOShenzhen的投票賬號:eoshenzhenio)!
EOS Todo Dapp - Free RAM Model and State Management - EOS + Redux = <3
You probably heard about the benefits of working with immutable states. That's the whole base of the blockchain, in ugly words, a big immutable database (decentralized of course). I'm not entering in details of the advantages of immutability, it's already changing the world and you can see it in our lovely EOS blockchain.
你可能聽說過保持不可變的狀態(tài)來進(jìn)行工作的好處,那是整個區(qū)塊鏈的基礎(chǔ),通俗來說,就是一個大的、不變的數(shù)據(jù)庫(當(dāng)然它是去中心化的)。我不需要詳細(xì)地來說明不可變性的優(yōu)點(diǎn),它已經(jīng)改變了世界,并且你可以在我們生機(jī)勃勃的EOS區(qū)塊鏈中看到它。
Now, inspired by the amazing DecentTwitter FREE RAM Dapp built by @kesarito and also by our outrageous EOS RAM price, I was studying and playing with Free RAM models and what should go inside EOS multi_index tables and what does not.
我的靈感來自于@kesarito打造的令人驚嘆的DecentTwitter FREE RAM Dapp,以及我們夸張的EOS RAM價格。我正在學(xué)習(xí)和使用免費(fèi)的RAM模型,以及考慮應(yīng)該在EOS多索引表格中使用什么,和不使用什么。
Functional Programming FTW
函數(shù)式編程增值
Working a lot in the last years with functional languages like Elixir and Elm, you have something in common: Immutability. The state is immutable and you actually use functions to update the state. You are not really updating a state, you are always creating a new one as it's immutable.
在過去的幾年里,你使用了諸如Elixir和Elm之類的函數(shù)式語言,你會發(fā)現(xiàn)它們有一些共同之處:不可變性。狀態(tài)是不可變的,實(shí)際上你是使用函數(shù)來更新狀態(tài)。你并不是在更新一個狀態(tài),而是在創(chuàng)建一個新的狀態(tài),因?yàn)樗冀K是不可變的。
I would like to pick The Elm Architecture in this example, as it is the perfect fit for browsers (which I think is the best use case in how to handle a million things running in parallel - and that's what EOS will achieve soon). So this is the Elm Model-View-Update Architecture:
我想在這個例子中選擇 Elm 架構(gòu),因?yàn)樗鼘τ跒g覽器是最佳選擇(我認(rèn)為它是闡釋如何并行處理百萬級交易的最佳用例——這就是EOS即將實(shí)現(xiàn)的目標(biāo))。 Elm模型 - 視圖 - 更新架構(gòu)如下所示:
Does it not look like theEOS Blockchain?
它看起來難道不像EOS區(qū)塊鏈嗎?
My dirty analogy of The Elm Architecture to EOS Blockchain Action/State update flow
Explaining my flow:
Actions: an user (or oracle) sign and push an action
Update State: this is our smart contract validating and updating the tables (OPTIONAL!)
Transaction: if your smart contract ran fine a new transaction is generated and added to the next block
Everything above on top of EOS Blockchain which is generating blocks (with or without new transactions)
Yeah ok, maybe I'm just forcing something here but you will understand what I want to get if you follow me along.
我將Elm架構(gòu)簡單地類推到EOS區(qū)塊鏈 Action/State更新流程
下面介紹我的流程:
1、Actions:用戶(或者oracle)簽名和推出一個action;
2、更新State:驗(yàn)證智能合約和更新表格(可選項(xiàng)?。?/p>
3、交易:如果你的智能合約運(yùn)行正常,則會生成新交易并將其添加到下一個區(qū)塊;
4、上面的一切都是在正在出塊的EOS區(qū)塊鏈之上的(不管有沒有新的交易)。
好吧,也許我在這里強(qiáng)加給你很多事情,但是如果你跟著我繼續(xù)了解下去,你就會明白我想要表達(dá)的是什么。
Optional RAM usage and introducing Redux to handle Data
可選的RAM使用和引入Redux來處理數(shù)據(jù)
In the above EOS Blockchain actions flow step 2 I said that update table is optional. And indeed it is, check DecentTwitter contract actions now:
在上述EOS區(qū)塊鏈行為流程步驟2中,我說更新表是可選的。 事實(shí)確實(shí)如此,現(xiàn)在檢查DecentTwitter合約action:
void tweet(std::string msg) {}
void reply(std::string id, std::string msg) {}
void avatar(std::string msg) {}
Fantastic! It just has empty body actions. It does not update any contract table, actually this contract has no table at all. You know what it means? There's no RAM cost. Free RAM Dapp!
太棒了! 它只有空白的action。 它不會更新任何合同表,實(shí)際上這個合同根本沒有表格。 你知道這意味著什么嗎? 沒有RAM成本!也就是這是免費(fèi)RAM 的分布式應(yīng)用!
The question here is: how to extract/generate state and handle the action data for more complex cases like an edit action? That's where I came up with Redux idea to handle the state. Redux was heavily inspired on Elm Architecture and that's why I introduced the whole Elm story above.
這里的問題是:如何提取/生成state并處理更復(fù)雜的情況(如編輯操作)的action數(shù)據(jù)? 這就是為什么我提出Redux想法來處理state。 Redux在Elm 結(jié)構(gòu)上受到了很大的啟發(fā),這也是我在上面介紹整個Elm故事的原因。
So what we want to do to have a safe and well-managed immutable state application from the blockchain is:
Listen to EOS blockchain actions relevant to our dapp, usually our contract actions only
Every time this new action arrives we dispatch a Redux action
Redux will handle the state update based on the action
I think nothing like a real example to illustrate the case. Let's create a FREE RAM TO-DO List Dapp! (cliche...)
因此,我們想要做的是,從區(qū)塊鏈中獲得一個安全的、管理良好的不可變state的應(yīng)用程序:
1、看看與我們的dapp相關(guān)的EOS區(qū)塊鏈action,通常只有我們的合約action;
2、每次有新的action,我們都會調(diào)用一個Redux action;
3、ReDux將根據(jù)action來進(jìn)行state的更新
我認(rèn)為沒有什么能像一個真實(shí)的例子來說明這個案例。讓我們創(chuàng)建一個免費(fèi)的RAM任務(wù)列表Dapp吧!(陳詞濫調(diào)…)
EOS To-Do List Dapp
EOS任務(wù)列表分布式應(yīng)用
This Dapp should allow us to handle our tasks in a to-do list fashion!
這個Dapp應(yīng)該能讓我們以待辦事項(xiàng)列表的方式來處理我們的任務(wù)!
EOS Smart Contract
EOS智能合約
This is atwenty-ishlines contract, super simple:
這是一個20行左右的合約,超級簡單:
#include <eosiolib/eosio.hpp>
using namespace eosio;
using std::string;
class todo : public eosio::contract {
public:
todo(account_name self)
:eosio::contract(self)
{}
void addtodo(uint64_t id, string text) {}
void edittodo(uint64_t id, string text) {}
void toggletodo(uint64_t id) {}
};
EOSIO_ABI( todo, (addtodo)(edittodo)(toggletodo) )
The contract has three simple empty body actions:
addtodo - allows you to create a new task on your todo list
edittodo - allows you to edit a task description on your todo list
toggletodo - allows you to mark a task as completed or unmark the completed flag
這個合約有三個簡單的、空白的action:
添加任務(wù)-允許你來創(chuàng)建一個新任務(wù)在你的任務(wù)列表
編輯任務(wù)-允許你來編輯一個任務(wù)內(nèi)容在你的任務(wù)列表
切換任務(wù)-允許你來標(biāo)記一個任務(wù)為已完成或者取消已完成的標(biāo)記
Node.js Backend EOS Blockchain Listener with Redux store management
Node.js后端EOS區(qū)塊鏈監(jiān)聽器和Redux存儲管理
Here I will explain how Redux will work and how I came up with this integration. If you can't understand it at all, please check https://redux.js.org/ - the Introduction and Basics chapter, real quick!
這里我將解釋Redux是如何工作的以及我是如何實(shí)現(xiàn)這個集成的。如果你根本無法理解,請查看https://redux.js.org/-介紹和基礎(chǔ)章節(jié),快速學(xué)習(xí)!
First step: setup the actions we want to listen from EOS Blockchain, in my case I deployed the above todo contract in an account called todo in my single local node:
第一步:設(shè)置我們監(jiān)聽EOS區(qū)塊鏈的action,在我的例子中,在我的單個本地節(jié)點(diǎn)的一個名為todo的賬戶中部署了上述任務(wù)合約:
// actions mapping
const ACTION_ADD_TODO = 'addtodo'
const ACTION_EDIT_TODO = 'edittodo'
const ACTION_TOGGLE_TODO = 'toggletodo'
const CONTRACT_ACTIONS = [
{
account: 'todo',
actions: [ACTION_ADD_TODO, ACTION_EDIT_TODO, ACTION_TOGGLE_TODO]
}
]
Second step: setup our initial state, how we want our store to look like. In our case it's just a simple todo list:
第二步:設(shè)置我們的初始state,以及希望的存儲是什么樣的。在例子中,這只是一個簡單的待辦事項(xiàng)列表:
// state
const initialState = {
todos: []
}
From the above state we will listen the EOS Blockchain actions to decide how to store and manage the data.
從上面的state,我們將觀察到EOS區(qū)塊鏈action,來決定如何存儲和管理數(shù)據(jù)。
Third step: setup the application reducer. Reducer (what originates the Redux name) is just a function that takes state and action as arguments, and returns the next state of the app. (Note the next state here, it's very important. Remember we are immutable, we don't change anything.)
第三步:設(shè)置應(yīng)用程序Reducer。Reducer.(源自Redux的名稱)只是一個將state和action作為參數(shù)的函數(shù),并返回應(yīng)用程序的下一個state(注意下一個state,它非常重要。記住,我們是不可變的,我們不會改變其中的任何東西。)
This is the application reducer code:
這里是應(yīng)用程序Reeducer的源代碼:
// reducer
const appReducer = (state = initialState, action) => {
switch (action.type) {
case ACTION_ADD_TODO:
return addTodoReducer(state, action)
case ACTION_EDIT_TODO:
return editTodoReducer(state, action)
case ACTION_TOGGLE_TODO:
return toggleTodoReducer(state, action)
default:
// return the current state
// if the action is unknown
return state
}
}
So in the above code we check the action type (action name from EOS) to decide which reducer we will call to update our state, if it's an unknown/not-mapped action it just ignores and returns the same state.
所以在上面的代碼中我們檢查action類型(來自EOS的action名稱)來決定我們將調(diào)用哪個Reducer來更新我們的state,如果它是一個未知/未映射的action,它只是忽略并返回相同的state。
Add Todo reducer function - it checks if the id was never used and add to our todo list. Each todo has the fields id, text, author and completed:
添加任務(wù)Reducer函數(shù) - 它會檢查id是否從未使用過并添加到我們的待辦事項(xiàng)列表中。 每個任務(wù)都有字段id,文本內(nèi)容,作者和是否完成標(biāo)記:
const addTodoReducer = (state, action) => {
// check and do not add new todo if this todo id
// already exists
if (state.todos.filter(todo => todo.id === action.data.id).length > 0)
return state
const newTodo = {
id: action.data.id,
text: action.data.text,
author: action.authorization[0].actor,
completed: false
}
const newTodos = [ ...state.todos, newTodo ]
return { ...state, todos: newTodos }
}
It's important to note above that we NEVER change the current state, we always create and return a new one. We do that because we are, again, immutable. It also allows us to have cool features like time-traveler debug, undo actions etc.
需要特別注意的是,我們從不改變當(dāng)前state,我們總是創(chuàng)建并返回一個新的state。這樣做是因?yàn)槲覀兪遣豢勺兊摹_@就允許我們有一些很酷的特性,比如實(shí)時的調(diào)試、撤銷action等等。
Edit todo reducer - here we simply update the text of a todo, ONLY IF the actor for this action is the same as the one that created this todo task:
編輯任務(wù)Reducer-在這里我們只是簡單地更新任務(wù)的文本,只有這個action的作者與任務(wù)的行為人相同時才會觸發(fā):
const editTodoReducer = (state, action) => {
const updatedTodos = state.todos.map(todo => {
if (todo.id === action.data.id &&
todo.author === action.authorization[0].actor) {
return {
...todo,
text: action.data.text // update text
}
} else {
return todo
}
})
return { ...state, todos: updatedTodos }
}
Toggle todo reducer - same as edit todo, it verifies if the actor is the same as the author of the task, this time it just toggles the completed boolean field:
切換任務(wù)Reducer-類似于編輯任務(wù),它驗(yàn)證行為人是否與任務(wù)的作者相同,這一次它只是用來切換表示是否完成的布爾字段:
const toggleTodoReducer = (state, action) => {
const updatedTodos = state.todos.map(todo => {
if (todo.id === action.data.id &&
todo.author === action.authorization[0].actor) {
return {
...todo,
completed: !todo.completed // toggle boolean
}
} else {
return todo
}
})
return { ...state, todos: updatedTodos }
}
Now the only thing left is to initialize the store! Here's the simple code:
現(xiàn)在剩下唯一的一件事就是來初始化存儲狀態(tài),這里是簡單的源代碼:
// initialize redux store
const store = createStore(appReducer)
// Log the initial state
console.log('>>> initial state: \n', store.getState(), '\n\n')
// Every time the state changes, log it
store.subscribe(() =>
console.log('>>> updated state: \n', store.getState(), '\n\n')
)
Actually only the first couple lines of the code is relevant. I'm just logging the initial state which prints this:
實(shí)際上,代碼的前幾行是相關(guān)的。我只是記錄并打印初始state:
>>> initial state:
{ todos: [] }
And subscribing the state which prints the full state each time that we update the state. The cool thing about Redux subscription is that you could use it to serve websockets to dapp users or write to your database, and so on. The possibilities are endless.
并且在每次更新state的時候,訂閱并打印完整state信息。關(guān)于Redux訂閱的一個很酷的事情是你可以用它來為dapp的用戶提供websockets或者寫入你的數(shù)據(jù)庫等等。 可能性是無限的。
Finally the last step is to dispatch the Redux actions every time that we have a relevant action. The blockchain listener that I built is not relevant, because you probably have yours already done, but what I'm basically doing is listening to each new block in the chain, checking if it has any transaction inside this block, selecting all the actions of it and finally filtering and dispatching each one to our reducer:
最后一步是每次我們有相關(guān)action時,調(diào)用Redux action。我建立的區(qū)塊鏈的監(jiān)聽器并不是相關(guān)的,因?yàn)槟憧赡芤呀?jīng)完成這個工作,但我主要做的是監(jiān)聽鏈中的每一個新塊,檢查是否有任何交易在這個塊中,選擇所有的action然后過濾,并且將每一個都分配給我們的Reducer:
const filterAndDispatchAction = (newAction, trxId, block) => {
const action = {
type: newAction.name,
account: newAction.account,
authorization: newAction.authorization,
data: newAction.data
}
const subscribed = CONTRACT_ACTIONS.find(item => (
item.account === action.account &&
item.actions.indexOf(action.type) >= 0
))
if (subscribed) {
console.log(`\nDispatching Action from Block ${block} - Trx ${trxId}:\n`,
action, '\n\n')
store.dispatch(action)
}
}
Remember the initial CONTRACT_ACTIONS we created in the beginning of this? Yes, it's finally being used to filter the relevant actions that you want.
還記得將我們一開始創(chuàng)建的CONTRACT_ACTIONS初始化嗎?對,它最終用于過濾你想執(zhí)行的相關(guān)action。
Also the store variable that contains the Redux state handler, is being used to dispatch the action received from the blockchain to our Redux store. This is how everything connects. If everything goes right you will see the following log in the console:
此外,包含Redux state處理程序的存儲變量被用于將區(qū)塊鏈接收的action,分派給我們的Redux存儲。 這就解釋了一切是如何連接。 如果一切順利,您將在控制臺中看到以下日志:
Dispatching Action from Block 81 - Trx a57a690978b78b18a3a5b2869d7734eb3da127147f256ff0ff74022f9adabd08:
{ type: 'addtodo',
account: 'todo',
authorization: [ { actor: 'eosio', permission: 'active' } ],
data: { id: 1, text: 'I need to add a database' } }
>>> updated state:
{ todos:
[ { id: 1,
text: 'I need to add a database',
author: 'eosio',
completed: false } ] }
Playing Todo Dapp using our backend Node.js Redux store application
使用我們的后端Dapp.js Redux存儲應(yīng)用程序播放Todo
The whole code is in this repository: https://github.com/leordev/eos-redux-todo
完整源代碼保存在這個地址中:https://github.com/leordev/eos-redux-todo
You can use the blockchain listener idea, it's super simple there and probably needs some polishing. Also I should refactor this code into different files, I just kept it simple with everything inside index.js
你可以使用區(qū)塊鏈監(jiān)聽器的想法,它真的非常簡單,可能需要一些修改。另外,我應(yīng)該將這些代碼重構(gòu)為不同的文件,我只是簡單地將其全部包含在了index.js中。
Instructions to Deploy the Contract
部署合約的指令
cleos create account eosio todo OWNERPUBKEY ACTIVEPUBKEY
cd eos-redux-todo/todo
eosiocpp -o todo.wast todo.cpp
eosiocpp -g todo.abi todo.cpp
cleos set contract todo ../todo
Instructions to Init the Nodejs Redux Store Application
初始化Nodejs Redux存儲應(yīng)用程序的指令
cd eos-redux-todo
npm install
node index.js
Demoing
演示
Wrapping it Up
包裝起來
That's it folks, that's the best way that I found to handle store management from EOS Blockchain Actions, using Redux which is a very popular and solid tool created by Dan Abramov from Facebook team. This is a nice library and has a lot of functionalities out-of-the-box like the subscription, it's easy to implement undo and time-traveling states. It's very mature and you have nice tooling around it like redux-logger and redux-devtools.
就是這些了,朋友們,這是我覺得解決來自EOS Blockchain Actions存儲管理的最佳方式,Redux是由Facebook團(tuán)隊(duì)的Dan Abramov創(chuàng)建的非常流行且可靠的工具。 這是一個很好的庫,并且具有許多開箱即用的功能,如訂閱,它很容易實(shí)現(xiàn)撤消和實(shí)時state。 你可以使用非常成熟的redux-logger和redux-devtools等等這些很好的工具。
The 100% free RAM model is an interesting approach but I think it has some flaws. E.g. the way I'm filtering the editing/toggling in the todo record by author, I think it should be blocked in the EOS chain not allowing the transaction to be created, but actually we don't have a table inside the contract to match the todo ids with the respective authors. So if we have other apps consuming this contract it could wrongly consider that the todo was edited by a bad actor if it does not take care of this very same filter rule that we did in our application.
完全免費(fèi)RAM模型是一個有趣的方法,但我認(rèn)為它有一些缺陷。 例如。 我通過作者過濾任務(wù)記錄中的編輯/切換操作,認(rèn)為這在EOS鏈中應(yīng)該被阻止并且不允許創(chuàng)建交易,但實(shí)際上我們在合同中沒有一個表格可以用來匹配任務(wù)id與各自的作者。因此,如果我們有其他應(yīng)用程序使用這個合約,如果不使用我們在應(yīng)用程序中所使用的相同的過濾規(guī)則,它可能會錯誤地認(rèn)為該任務(wù)是由一個非法用戶編輯的。
I would love to hear your feedbacks in how you are managing this and your thoughts in this approach. After we have solid progress in this Redux integration and some use cases with standardized actions I could wrap it up in a JS library and integrate with eosjs. That would be nice, just not sure if it makes sense yet. See you all!
我很想聽聽你對此方法的反饋以及你對此方法的看法。在我們在Redux集成和一些使用標(biāo)準(zhǔn)化操作的用例中取得了穩(wěn)定的進(jìn)展之后,我可以將它封裝在JS庫中,并與eosjs集成。能做到的話將會很好,只是不確定它是否有意義。下次見!
本文圖片來源于英文原文
了解更多關(guān)于EOShenzhen:
關(guān)于我們更多聯(lián)系:
Website:https://eoshenzhen.io
Steem:https://steemit.com/@eoshenzhen
Busy:https://busy.org/@eoshenzhen
Telegram:https://t.me/eoshenzhen
Twitter:https://twitter.com/eostechlover
簡書:EOS技術(shù)愛好者
新浪微博:EOSTechLover
EOShenzhen的投票賬號:eoshenzhenio