stamp sheet
GS2 uses a transaction system called “Stamp Sheets” to link services.
Among the APIs in GS2, operations that are disadvantageous to the player are called “Consume Action”, while operations that are beneficial to the player are called “Acquire Action”.
Let’s say there is an action in the game store to “Spend 1000 Gems” and “Draw 10 times in the Gacha”. In this case, “Spend 1000 Gems” can be considered a “Consume Action” and “Draw 10 times in the Gacha” can be considered an “Acquire Action”.
Let’s look at a few more examples.
- Execute the “Read Message” consume action and then execute the “Receive 100 Gems attached to the message” acquisition action.
- Performing the “Consume 10 Stamina” consume action and then performing the “Set Quest 1 as Started” acquisition action
- Perform a “Delete Quest 1 Started” consume action to “Receive the reward for clearing Quest 1” acquisition action.
- Execute the “Update Last Abandonment Reward Receiving Time with Current Time” consume action to “Get the reward according to the abandonment time” acquisition action.
- Perform a consume action to “Raise the daily reset counter by 1” to “Obtain an item that cannot be received more than once a day” to perform the “Obtain an item that cannot be received more than once a day” action.
Thus, in GS2, every game cycle is represented by consuming something and getting something.
How Stamp Sheets Work
A stamp sheet consists of several “Consume Actions” and one “Acquire Action”, Stamp sheets are issued by GS2 microservices such as the Store and Quest functions. The contents of the stamp sheet are determined based on the product master data registered in the store function.
Stamp sheets are executed by
Execution of "Consume Action" -> Execution of "Acquire Action".
The stamp sheets are processed in this order.
This order of processing prevents unauthorized repeated execution of the “Acquire Action”.
When the “Consume Action” is executed, the microservice that provides the GS2 functionality will issue a signature to prove that it has been executed. When a “Acquire Action” is executed, the signatures received from all “Consume Actions” are sent together. Signature verification is performed before executing the “get action” to ensure that everything has been done before executing the “get action”.
Service Discovery
Depending on the contents of the “Consume Action” or “Acquire Action” on the stamp sheet, the appropriate microservice must be requested to perform the processing. However, GS2’s functionality is constantly being added, and the number of “Consume Action” and “Acquire Action” types continues to increase, making it difficult to maintain such branching processes.
For this reason, GS2 provides GS2-Distributor, a microservice that forwards the received stamp sheets to the appropriate microservice. By sending a stamp sheet to GS2-Distributor, GS2-Distributor is responsible for forwarding the sheet to the appropriate microservice according to the “Consume Action” or “Acquire Action” on the stamp sheet.
Preventing duplicate executions
Stamp sheets are designed in such a way that they cannot be duplicated. In fact, all GS2 Consume Actions and Acquire Actions APIs have a mechanism to prevent duplicate execution.
The “Consume Action” and “Acquire Action” APIs accept a “Duplication Avoider” parameter. If a value is specified here and the API completes successfully, the response will be saved by GS2 for a certain period of time. If the “Duplication Avoider” is specified in the same request payload, and if a process with a matching “Duplication Avoider” value has been executed in the past, the response will be treated as a normal completion and no processing will actually take place. In addition, when executing a “Consume Action” or “Acquire Action” using a stamp sheet, the “Duplication Avoider” should be set to “Transaction ID,” which is a unique ID for the stamp sheet.
Let’s take a step-by-step look at how the duplication prevention mechanism is implemented. First, let us assume that the following stamp sheets exist.
Consume Action | Acquire Action |
---|---|
Consume 1 item | Obtain 10 gems |
Increase the frequency limit counter by 1 |
When executing this stamp sheet, first execute “Consume 1 item”.
Consume Action | Acquire Action |
---|---|
Get 10 Gems | |
Increase the frequency limit counter by 1 |
Then, you execute “Increase the Count Limit Counter by 1”, but a server error occurs. At this time, the item has already been consumed and stopped, and if we leave it as is, we will receive complaints from players.
Therefore, we retry to run the stamp sheet.
In the retry, the stamp sheet is run again from the beginning. First, execute “Consume 1 item”. This is where the “Duplication Avoider” comes in handy. Since the item has already been consumed, it is not actually consumed, but the response from the previous successful attempt is returned as normal completion. The next step is to execute “Increase Count Limit Counter by 1” since it looks like just a success response from the stamp sheet executor’s point of view.
Consume Action | Acquire Action |
---|---|
Obtain 10 gems | |
The next one succeeded successfully.
Finally, the acquisition action, “Get 10 Gems,” is performed.
Consume Action | Acquire Action |
---|---|
~~Consume 1 item~ | |
This completes the role of the stamp sheet. Please understand that if you fail to execute a stamp sheet, you can simply retry without thinking about how far you have gone.
Automate stamp sheet retries
Although we have said that you should retry when a stamp sheet fails to execute, it is actually difficult to do so thoroughly, even though it sounds simple. Therefore, the GS2 microservice that issues stamp sheets has an option called “Automatically execute stamp sheets”. If this option is enabled, the server side will automatically retry when a retry is necessary for some reason.
If the retry does not solve the problem, such as “not enough items left to spend in the consume action,” the retry may not be performed.
Multiple Acquire Action
You may think that multiple Acquire Action are acceptable if there is a mechanism to prevent duplicate execution. That is basically correct. However, it is important to note that the retention period for response content to prevent duplicate execution is not indefinite. If the retention period for response content is exceeded, it will be possible to execute a stamp sheet issued in the past again. In this case, it is still necessary to execute the consume action, but even if the consume action is executed, there are cases where it is undesirable to execute it again. To address this problem, a process is incorporated to invalidate the stamp sheet itself when the stamp sheet execution is completed, so that it cannot be re-executed even if the response content retention period has passed.
If there are multiple “obtain actions” here, it will be difficult to know when to deactivate the stamp sheet. For this reason, the stamp sheet is designed so that only one “Acquire Action” can be set.
However, as a practical matter, it is not always possible to have only one acquisition action in a game. Even in the most common game cycle, the quest function, two Acquire Action occur at the same time: “gain experience” and “drop item acquisition.
To handle such cases, GS2 makes use of job queues. A job queue is a queue prepared for each player for delayed execution.
In a situation where you want to set up a “get action” on a stamp sheet, you set up a single “get action” by “registering multiple jobs to perform get actions in the job queue”. This process is automated, and multiple “Acquire Actions” can be set when setting up rewards in the quest master data. When issuing a stamp sheet, this is transformed into “registering multiple jobs to perform an acquisition action” and the stamp sheet is issued.
Stamp sheet execution is an asynchronous process
As we have seen, it is very difficult to wait for a stamp sheet to complete. GS2 provides a number of microservices, and even if one of them stops, the entire service is designed not to stop. However, when a failure occurs, the execution of stamp sheets and job queues stalls, and there is no guarantee that the results will be reflected immediately.
On the other hand, when a failure is restored, you do not have to do anything, but the stamp sheet and job queue will retry and the process will begin to flow, and eventually normalize. You may be puzzled because this is different from the conventional development style. However, GS2 adopts this design for the overall benefit that even if a temporary inconsistency occurs due to a partial microservice failure, the process will eventually be normalized after the failure is restored without having to think about it.
Setting Parameters when Issuing Stamp Sheets
Requests to each service require information about which user’s resources are to be manipulated, though, However, it is not possible to statically specify user IDs in advance in the in-game store or quest master data. Therefore, a variable can be embedded in the stamp sheet request.
If you set the placeholder string #{userId} in the request for the master data action, This will be replaced by the user ID of the user who issued the stamp sheet when the stamp sheet is issued.
Config
A parameter called Config(EzConfig) can be passed to the stamp sheet issuance request. Config(EzConfig) is a key/value format that allows you to replace the placeholder string #{key value specified in Config} with the passed parameter.
Result of Stamp Task Execution If you set the placeholder string ${Gs2Money:WithdrawByUserId.price} as an example in the description of the action request, The placeholder string will be replaced by the result of the stamp task execution and can be used as a variable. In the case shown in the example, the result of the Gs2Money:WithdrawByUserId execution of the executed task is referenced and the returned price is used as the value. Child elements can be referenced by connecting them with dots like ${Gs2Money:WithdrawByUserId.item.paid}.
The value adopted when the same action is registered as multiple stamp tasks is undefined.
Example of a stamp sheet that adds a balance to a wallet.
{
"name": "currency-120-jpy",
"metadata": "price: 120 currencyCount: 50",
"consumeActions": [
{
"action": "Gs2Money:RecordReceipt",
"request": "{\"namespaceName\": \"money-0001\", \"contentsId\": \"io.gs2.sample.currency120\", \"userId\": \"#{userId}\", \"receipt\": \"#{receipt}\"}"
}
],
"acquireActions": [
{
"action": "Gs2Money:DepositByUserId",
"request": "{\"namespaceName\": \"money-0001\", \"userId\": \"#{userId}\", \"slot\": \"#{slot}\", \"price\": 120, \"count\": 50}"
}
]
}
Example of setting Config values from Unity
var result = await gs2.Showcase.Namespace(
namespaceName: "namespace-0001"
).Me(
gameSession: GameSession
).Showcase(
showcaseName: "showcase-0001"
).BuyAsync(
displayItemId: "display-item-0001",
quantity: 1,
config: new [] {
new EzConfig
{
Key = "slot",
Value = Slot.ToString(),
},
new EzConfig
{
Key = "receipt",
Value = receipt,
},
}
);