Hyperledger Composer中能否创建只读事务?新手实操问询
Hey there! I totally get where you're coming from as a Hyperledger Composer newbie—this was a confusing spot for me too when I started out. It's easy to think transactions are only for modifying data, but you can absolutely build read-only transactions that fetch, process, and return custom results. Let me walk you through exactly how to do this.
Step 1: Define Your Assets, Result Concept, and Transaction in the CTO File
First, you need to formalize your asset types, a custom concept to hold your processed results, and the read-only transaction itself in your model file (.cto):
namespace org.example.myapp // Example Asset Type 1 asset AssetA identified by assetAId { o String assetAId o Integer value } // Example Asset Type 2 asset AssetB identified by assetBId { o String assetBId o Integer quantity } // A custom concept to structure your processed output concept ProcessedResult { o Integer totalCalculatedValue o String summaryMessage o AssetA[] matchingAssetsA o AssetB[] matchingAssetsB } // The read-only transaction (add parameters here if you need filters) transaction FetchAndProcessAssets { // Optional: Add filter parameters like o String assetCategory if needed }
Step 2: Write the Transaction Processor Logic
Next, create a JavaScript transaction processor that fetches both asset types, runs your custom calculations, and returns the processed result. This is where all the heavy lifting happens:
/** * Fetch, process, and return combined data from AssetA and AssetB * @param {org.example.myapp.FetchAndProcessAssets} tx * @transaction */ async function fetchAndProcessAssets(tx) { // Fetch all AssetA records (add a WHERE clause to buildQuery if you need filtering) const assetAQuery = buildQuery('SELECT org.example.myapp.AssetA'); const assetARecords = await query(assetAQuery); // Fetch all AssetB records const assetBQuery = buildQuery('SELECT org.example.myapp.AssetB'); const assetBRecords = await query(assetBQuery); // Run your custom math/processing logic here let totalValue = 0; assetARecords.forEach(asset => { totalValue += asset.value; // Sum values from AssetA }); assetBRecords.forEach(asset => { totalValue += asset.quantity * 15; // Example: Multiply AssetB quantity by 15 and add to total }); // Build your custom result object const factory = getFactory(); const processedResult = factory.newConcept('org.example.myapp', 'ProcessedResult'); processedResult.totalCalculatedValue = totalValue; processedResult.summaryMessage = `Processed ${assetARecords.length} AssetA and ${assetBRecords.length} AssetB records. Total calculated value: ${totalValue}`; processedResult.matchingAssetsA = assetARecords; processedResult.matchingAssetsB = assetBRecords; // Return the custom result to the caller return processedResult; }
Step 3: Call the Transaction and Get Your Result
Once you've deployed your updated business network, you can call this transaction just like any other:
- Via Composer REST API: Send a POST request to the
/transactionsendpoint with the transaction typeorg.example.myapp.FetchAndProcessAssets(and any parameters if you added them). The response will be yourProcessedResultobject. - Via CLI: Use
composer transaction submit -n your-network-name -c admin@your-network-name -d '{"$class":"org.example.myapp.FetchAndProcessAssets"}'—the output will show your processed result.
Why This Works (And Why Queries Aren't Enough)
You're right that the built-in query system (queries.qry) is limited to fetching data without custom processing. Transactions, however, are designed to run arbitrary logic—including read-only operations. The key here is defining a custom Concept to shape your return data, since Composer needs a formal structure to serialize the response back to the caller.
内容的提问来源于stack exchange,提问作者parth.patil

