Requests API
Table of contents
Add Request API to dapr.yaml
As we will be having many dapr applications, let’s leverage the multi-run feature of Dapr to run all the applications at once. For now, let’s add the request api only to the dapr.yaml file.
- Open the
dapr.yamlfile in the/folder and add a new node for the application including the environment variables.
Toggle solution
- appID: summarizer-requests-api
appDirPath: ./src/requests-api/
appPort: 13000
command: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "13000"]
env:
STATE_STORE_NAME: "summarizer-statestore"
STATE_STORE_QUERY_INDEX_NAME: "orgIndx"
BINDING_SMTP: "summarizer-smtp"
APP_PORT: 13000
Note: The
appIDis used to identify the application in the Dapr runtime. TheappDirPathis the path to the application folder. TheappPortis the port used by the application. Thecommandis the command used to start the application. Theenvis the environment variables used by the application.
Create State Store yaml
From the previous definition of the environment variables of our application, we can see that we will be using a state store. Let’s create the yaml file for the state store.
- Create a new file named
summarizer-statestore.yamlin the/dapr/local/componentsfolder, add a state store component called ‘summarizer-statestore’ and configure it to use Redis. Query indexes should also be configured to allow for fast retrieval of data (url, url_hashed and id).
Toggle solution
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: summarizer-statestore
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
- name: queryIndexes
value: |
[
{
"name": "orgIndx",
"indexes": [
{
"key": "id",
"type": "TEXT"
},
{
"key": "url",
"type": "TEXT"
},
{
"key": "url_hashed",
"type": "TEXT"
}
]
}
]
Note: The
nameis the name of the component. Thetypeis the type of the component. Theversionis the version of the component. Themetadatais the configuration of the component. We also added a query index to the state store to be able to query the state store by url. url_hashed is the hashed version of the url to ease the search query and avoid any special characters issues.
Request API Overview
- Open the
app.pyfile in the/src/requests-apifolder, and notice the DaprClient object that is used to interact with the Dapr runtime.
dapr_client = DaprClient()
- We also have several endpoints defined to manage the requests lifecycle :
@app.get('/requests'): Get all the requests@app.post('/search-requests-by-url'): Search a request with specific link@app.post('/requests', status_code = status.HTTP_201_CREATED): Create a request
Implementing state and SMTP binding methods
Let’s implement the state methods and the SMTP output binding.
- Open the
request_state.pyfile in the/src/requests-apifolder, managing the state store.
def __query_state(self, query : str)is using the DaprClient object to execute the query to the state store.
- Fill the
find_allmethod to get all the requests from the state store. The method should return a list ofRequestobjects.
Toggle solution
def find_all(self, token: str = None):
try:
# Paging are supported using token
# see https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-state-query-api/
token = f", \"token\": \"{token}\"" if token is not None else ""
query = '''
{{
"page": {{
"limit": 100{0}
}}
}}
'''.format(token)
return self.__query_state(query)
except Exception as e:
logging.error(
f"Error while trying to find all requests within the dapr state store")
logging.error(e)
return []
- Fill the
__try_filtermethod to get a request from the state store using a specific property and value. The method should return aRequestobject or None.
Toggle solution
def __try_filter(self, property : str, value : str):
try:
query = '''
{{
"filter": {{
"EQ": {{ "{0}": "{1}" }}
}}
}}
'''.format(property, value)
results = self.__query_state(query)
return results[0] if len(results) > 0 else None
except Exception as e:
logging.error(
f"Error while trying to find request using {property} and {value} within the dapr state store")
logging.error(e)
return None
- Fill the
__alert_ownermethod to invoke the smtp binding, providing an email contents as a data.None.
Toggle solution
def __alert_owner(self, request: SummarizeRequest):
# Send email to requestor
email_contents = """
<html>
<body>
<p>Hi,</p>
<p>Here is the summary for the article you requested:</p>
<p><a href="{url}">{url}</a></p>
<p>{summary}</p>
<p>Thanks for using our service!</p>
</body>
</html>
""".format(url=request.get_url(), summary=request.get_summary())
self.dapr_client.invoke_binding(
binding_name=self.settings.binding_smtp,
operation='create',
data=json.dumps(email_contents),
binding_metadata={
"emailTo": request.get_email(),
"subject": f"🎉 New Summary for {request.get_url()}!"
}
)
Validate that the request is stored in the state store and email received
-
Execute dapr run multi run command to start the application
dapr run -f . -
Open a new terminal and use curl to validate new request creation
-
Check that the request is stored in the state store and email received