API-Led Example: MuleSoft
This article takes a look at API-led architecture using Mule4 APIs in order to give a clear understanding of the development process of an API-led approach.
Join the DZone community and get the full member experience.
Join For FreeThis article gives an impression of API-led architecture using Mule4 APIs in order to give a clear understanding of the development process of an API-led approach. It covers all 3 of the API layers (System, Process, and Experience) using Salesforce and FIRST.org as the source. Additionally, we cover applying best practices in each layer of development.
- RAML:
Common library
,traits
,data definition
,reusable resources
,health endpoint
- API:
Externalize property files
,encryption of properties
,externalize dwl code
,reusable http requester
,common error handling
,applying policies
,applying loggings
Requirements
- Create and get account details from Salesforce CRM
- Input has country code in the request for shipping and billing details tags, which needs to be looked up against FIRST.org open source REST API to get the country name
- Implement
clientID
enforcement policy with SLA-based rate limiting; limit 100 requests in a minute - Follow the best practices:
- Reusable traits, library, and response code in RAML
- Define respective APIs with proper error handling,
global connectors
,logging
,munit testcases
Common Library
Common Traits
- Client credentials headers: To re-use for APIs where the developer wants to secure the resources using the Client ID enforcement policy
#%RAML 1.0 Trait
headers:
client_id:
type: string
description: Client Id for the respective consumer application
client_secret:
type: string
description: Client Secret for the respective consumer application
- Rate limit headers: To re-use for APIs where the rate-limiting policy will be applied
#%RAML 1.0 Trait
usage: Apply rate limiting header to indicate consumer about rate limiting policy
responses:
201:
headers:
X-Ratelimit-Remaining:
type: number
description: The amount of available quota
example: 5
X-Ratelimit-Limit:
type: number
description: The maximum available requests per window
example: 10
X-Ratelimit-Reset:
type: number
description: The remaining time, in milliseconds, until a new window starts
example: 3000
Common Health Endpoint
To get the health/heartbeat of an API:
#%RAML 1.0 Library
#This library defines the health resource type
resourceTypes:
health:
usage: Use this resource to check health of Mulesoft application
description: Entity representing a Mulesoft application health
get:
description: Get health of an application
Error Response
To define common error codes along with error details:
#%RAML 1.0 DataType
properties:
code:
type: integer
required: false
message:
type: string
required: false
Example:
#%RAML 1.0 NamedExample
code: 400
message: The request could not be understood by the server due to malformed syntax.
Please follow here for the common-lib-1.0.0-fat-raml-fragment
code.
accounts-sfdc-sapi
RAML
- Define
/accounts
toPOST
the SF account details. - Define
/accounts/{accountId}
to performGET
,PUT
, andDELETE
operations on specific SF accounts. - Refer common client credentials header for client app validation.
EXPERIENCE
orPROCESS API
will be the client for this API.
#%RAML 1.0
title: SFDC accounts system api
description: API to handle the salesforce account integration
version: 1.0
traits:
header-client-credentials-required: !include traits/header-client-credentials-required.raml
types:
address: !include /account/dataTypes/reusable/address.raml
account: !include /account/dataTypes/reusable/account.raml
account-request: !include /account/dataTypes/account-request.raml
resourceTypes:
account: !include /account/resourceType.raml
/accounts:
type: account
post:
/{accountId}:
type: account
get:
put:
delete:
Please follow here for the accounts-sfdc-sapi
RAML code.
API Implementation
Define flows to perform the CRUD
operations based on input provided by the process
or exp
API. The API is secured with the Client ID enforcement policy, so make sure of valid client access.
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xmlns:salesforce="http://www.mulesoft.org/schema/mule/salesforce"
xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/salesforce http://www.mulesoft.org/schema/mule/salesforce/current/mule-salesforce.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
<sub-flow name="create-account" doc:id="a2e406fe-32b1-4597-a6b0-1f294cb44b23" >
<ee:transform doc:name="Prepare reqest payload" doc:id="bc9ab37d-e915-445c-a6a3-551c5fbb576a" >
<ee:message >
<ee:set-payload resource="dwl/createAccountRequest.dwl"/>
</ee:message>
</ee:transform>
<salesforce:create doc:name="Create Account" doc:id="37ed697b-edfb-46cc-bf85-b605f9f2aaed" config-ref="Salesforce_Config" type="Account" target="responseData" targetValue="#[payload]"/>
<logger level="DEBUG" doc:name="DEBUG : create account response" doc:id="db6d4530-7bea-4386-aed9-dd69022fe115" message='#[payload]'/>
<ee:transform doc:name="Prepare response payload" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:message >
<ee:set-payload resource="dwl/createAccountResponse.dwl"/>
</ee:message>
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[201]]></ee:set-variable>
</ee:variables>
</ee:transform>
</sub-flow>
<sub-flow name="update-account" doc:id="a2e406fe-32b1-4597-a6b0-1f294cb44b23" >
<ee:transform doc:name="Prepare reqest payload" doc:id="bc9ab37d-e915-445c-a6a3-551c5fbb576a" >
<ee:message >
<ee:set-payload resource="dwl/updateAccountRequest.dwl"/>
</ee:message>
</ee:transform>
<salesforce:update doc:name="Update Account" doc:id="37ed697b-edfb-46cc-bf85-b605f9f2aaed" config-ref="Salesforce_Config" type="Account" target="responseData" targetValue="#[payload]"/>
<logger level="DEBUG" doc:name="DEBUG : update account response" doc:id="db6d4530-7bea-4386-aed9-dd69022fe115" message='#[payload]'/>
<ee:transform doc:name="Prepare response payload" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:message >
<ee:set-payload resource="dwl/updateAccountResponse.dwl"/>
</ee:message>
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[201]]></ee:set-variable>
</ee:variables>
</ee:transform>
</sub-flow>
<sub-flow name="get-account" doc:id="eee0997c-7427-4292-8cdc-ea7fc5882b10" >
<salesforce:retrieve doc:name="Retrieve Account" doc:id="e797b1c6-dea3-4288-9f77-59b2b02ee15e" config-ref="Salesforce_Config" type="Account" target="responseData" targetValue="#[payload]">
<salesforce:retrieve-request ><![CDATA[#[output application/java
---
{
fields: ['Name,AccountNumber,Phone,Fax,BillingStreet,BillingCity,BillingState,BillingPostalCode,BillingCountry,ShippingStreet,ShippingCity,ShippingState,ShippingPostalCode,ShippingCountry'],
ids: [vars.accountId]
} as Object {
class : "org.mule.extension.salesforce.api.core.RetrieveRequest"
}]]]></salesforce:retrieve-request>
</salesforce:retrieve>
<logger level="DEBUG" doc:name="DEBUG : get account response" doc:id="db6d4530-7bea-4386-aed9-dd69022fe115" message='#[payload]'/>
<ee:transform doc:name="Prepare response payload" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:message >
<ee:set-payload resource="dwl/getAccountResponse.dwl"/>
</ee:message>
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[200]]></ee:set-variable>
</ee:variables>
</ee:transform>
</sub-flow>
<sub-flow name="delete-account" doc:id="3e956c02-f292-4d26-aede-fea6f67042c7" >
<salesforce:delete doc:name="Delete Account" doc:id="ea97702a-7b84-4f9b-b828-927adf3934f0" config-ref="Salesforce_Config" target="responseData" targetValue="#[payload]">
<salesforce:ids ><![CDATA[#[output application/java
---
[vars.accountId]]]]></salesforce:ids>
</salesforce:delete>
<logger level="DEBUG" doc:name="DEBUG : delete account response" doc:id="db6d4530-7bea-4386-aed9-dd69022fe115" message='#[payload]'/>
<ee:transform doc:name="Prepare response payload" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:message >
<ee:set-payload resource="dwl/deleteAccountResponse.dwl"/>
</ee:message>
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[200]]></ee:set-variable>
</ee:variables>
</ee:transform>
</sub-flow>
</mule>
Please follow here for the accounts-sfdc-sapi
code.
first-country-lookup-sapi
API Implementation
Define flow to get the country details from api.first.org. The API is secured with the Client ID enforcement policy.
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
<sub-flow name="initiate-get-countries" doc:id="eee0997c-7427-4292-8cdc-ea7fc5882b10" >
<logger level="INFO" doc:name="INFO : Flow Start Logger" doc:id="04e1f991-3f9a-41e1-b123-1c9ac0adbde7" message='#["===== Before HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
<http:request method="GET" doc:name="Request to system api" doc:id="52384f97-346f-4ed7-9416-41838f5cde53" config-ref="FIRST_SAPI_HTTP_Request_configuration" path="${secure::http.requester.first_api.path}" responseTimeout="${secure::http.requester.first_api.response_timeout}">
<reconnect frequency="${secure::http.requester.first_api.reconnection_frequency}"></reconnect>
<http:response-validator >
<http:success-status-code-validator values="200" ></http:success>
</http:response-validator>
</http:request>
<logger level="DEBUG" doc:name="DEBUG : Countries Response" doc:id="db6d4530-7bea-4386-aed9-dd69022fe115" message='#[payload]'></logger>
<ee:transform doc:name="Prepare response header" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[200]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="INFO" doc:name="INFO : Flow End Logger" doc:id="ece82a56-9ad8-4ae6-8f5b-aa4db5246564" message='#["===== After HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
</sub-flow>
</mule>
Please follow here for the first-country-lookup-sapi
code.
accounts-papi
RAML
- Define
/accounts
to post the data received fromEXPERIENCE API
. Based on the country lookup response fromSYSTEM API
, post the account details. - Define
/accounts/{accountId}
to update the account details received fromEXPERIENCE API
. Based on country lookup, it will update the account details. - Refer common client credentials header for client APP validation.
EXPERIENCE API
will be the client for this API.
#%RAML 1.0
title: Salesforce Accounts Process API
description: API to handle the salesforce account integration
version: 1.0
traits:
header-client-credentials-required: !include traits/header-client-credentials-required.raml
types:
address: !include /account/dataTypes/reusable/address.raml
account: !include /account/dataTypes/reusable/account.raml
account-request: !include /account/dataTypes/account-request.raml
account-response: !include /account/dataTypes/account-response.raml
resourceTypes:
account: !include /account/resourceType.raml
/accounts:
type: account
post:
/{accountId}:
type: account
put:
Please follow here for the accounts-papi
RAML code.
API Implementation
- Define flow to add new account details received from experience API.
- Define flow to gather country details from system API and pass it to SFDC system API based on details received from experience API.
- API is secured with the Client ID enforcement policy.
<flow name="post:\accounts:application\json:accounts-papi-config">
<logger level="INFO" doc:name="INFO : Flow Start Logger" doc:id="6b70cf63-c681-489b-80c1-23dc98f5d9df" message="#["===== Start of " ++ flow.name ++ " flow ====="]" ></logger>
<ee:transform doc:name="capture request info" doc:id="ea5a1ca4-6d61-4f24-822c-f7e4e58c0f5f">
<ee:variables>
<ee:set-variable variableName="expAPIData"><![CDATA[%dw 2.0
output application/json
---
payload]]></ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="country-lookup" doc:name="country-lookup" target="countryLookup" targetValue="#[payload]"></flow>
<flow-ref name="initiate-create-account" doc:name="initiate-create-account" ></flow>
<ee:transform doc:name="Prepare response code" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b">
<ee:variables>
<ee:set-variable variableName="httpStatus"><![CDATA[201]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="INFO" doc:name="INFO : Flow End Logger" doc:id="4b94b57b-e282-4973-b0d7-9b5398a24869" message="#["===== End of " ++ flow.name ++ " flow ====="]" ></logger>
</flow>
<flow name="put:\accounts\(accountId):application\json:accounts-papi-config">
<logger level="INFO" doc:name="INFO : Flow Start Logger" doc:id="6b70cf63-c681-489b-80c1-23dc98f5d9df" message="#["===== Start of " ++ flow.name ++ " flow ====="]" ></logger>
<ee:transform doc:name="capture request info" doc:id="ea5a1ca4-6d61-4f24-822c-f7e4e58c0f5f">
<ee:variables>
<ee:set-variable variableName="expAPIData"><![CDATA[%dw 2.0
output application/json
---
payload]]></ee:set-variable>
<ee:set-variable variableName="accountId"><![CDATA[%dw 2.0
output application/java
---
attributes.uriParams.'accountId']]></ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="country-lookup" doc:name="country-lookup" target="countryLookup" targetValue="#[payload]"></flow>
<flow-ref name="initiate-update-account" doc:name="initiate-update-account" ></flow>
<ee:transform doc:name="Prepare response code" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b">
<ee:variables>
<ee:set-variable variableName="httpStatus"><![CDATA[201]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="INFO" doc:name="INFO : Flow End Logger" doc:id="4b94b57b-e282-4973-b0d7-9b5398a24869" message="#["===== End of " ++ flow.name ++ " flow ====="]" ></logger>
</flow>
Please follow here for the accounts-papi
code.
mobile-accounts-eapi
RAML
- Define
/accounts
to post the SF account details. - Define
/accounts/{accountId}
to performGET
,PUT
, andDELETEs
operations on specific SF accounts. - Refer common client credentials header for client app validation. The mobile app will be the client for this API.
- Refer common rate limit header to limit the requests from the mobile app.
#%RAML 1.0
title: Mobile Accounts Experience API
mediaType:
- application/json
description: API to handle the salesforce account integration along with first org country lookup
version: 1.0
protocols: [ HTTP, HTTPS ]
baseUri: api/{version}
uses:
commonLib: /exchange_modules/3a821d74-ead4-48a9-87e6-5bb67f180d55/common-lib/1.0.0/libraries/health.raml
traits:
header-client-credentials-required: !include /exchange_modules/3a821d74-ead4-48a9-87e6-5bb67f180d55/common-lib/1.0.0/traits/header-client-credentials-required.raml
header-rate-limit-required: !include /exchange_modules/3a821d74-ead4-48a9-87e6-5bb67f180d55/common-lib/1.0.0/traits/header-client-credentials-required.raml
error-400: !include /exchange_modules/3a821d74-ead4-48a9-87e6-5bb67f180d55/common-lib/1.0.0/traits/errors/json/400.raml
error-404: !include /exchange_modules/3a821d74-ead4-48a9-87e6-5bb67f180d55/common-lib/1.0.0/traits/errors/json/404.raml
error-500: !include /exchange_modules/3a821d74-ead4-48a9-87e6-5bb67f180d55/common-lib/1.0.0/traits/errors/json/500.raml
types:
address: !include /account/dataTypes/reusable/address.raml
account: !include /account/dataTypes/reusable/account.raml
account-request: !include /account/dataTypes/account-request.raml
account-response: !include /account/dataTypes/account-response.raml
resourceTypes:
account: !include /account/resourceType.raml
/health:
type:
commonLib.health
/accounts:
type: account
post:
/{accountId}:
type: account
get:
put:
delete:
Please follow here for the mobile-accounts-eapi
RAML code.
API Implementation
- Define flows to perform operations based on the requests from end clients; i.e., mobile app.
- API is secured with a rate-limiting policy to control the request flows to MuleSoft.
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:ee="http://www.mulesoft.org/schema/mule/ee/core"
xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/ee/core http://www.mulesoft.org/schema/mule/ee/core/current/mule-ee.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd">
<sub-flow name="initiate-create-account" doc:id="a2e406fe-32b1-4597-a6b0-1f294cb44b23" >
<logger level="DEBUG" doc:name="DEBUG : Flow Start Logger" doc:id="30c39831-908e-47e3-bc04-8ea64ae26e15" message='#["===== Before HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
<ee:transform doc:name="Prepare request parameter" doc:id="64b2a22a-702d-4824-93df-54d28b6a9eae" >
<ee:variables >
<ee:set-variable variableName="requestParameter" ><![CDATA[%dw 2.0
output application/java
---
{
"method" : "POST",
"path" : ""
}]]></ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="common-201-requester-flow" doc:name="common-201-requester-flow"></flow>
<ee:transform doc:name="Prepare response code" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[201]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="DEBUG" doc:name="DEBUG : Flow End Logger" doc:id="ece82a56-9ad8-4ae6-8f5b-aa4db5246564" message='#["===== After HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
</sub-flow>
<sub-flow name="initiate-update-account" doc:id="a2e406fe-32b1-4597-a6b0-1f294cb44b23" >
<logger level="DEBUG" doc:name="DEBUG : Flow Start Logger" doc:id="04e1f991-3f9a-41e1-b123-1c9ac0adbde7" message='#["===== Before HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
<ee:transform doc:name="Prepare request parameter" doc:id="64b2a22a-702d-4824-93df-54d28b6a9eae" >
<ee:variables >
<ee:set-variable variableName="requestParameter" ><![CDATA[%dw 2.0
output application/java
---
{
"method" : "PUT",
"path" : '/' ++ attributes.uriParams.'accountId'
}]]></ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="common-201-requester-flow" doc:name="common-201-requester-flow"></flow>
<ee:transform doc:name="Prepare response code" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[201]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="DEBUG" doc:name="DEBUG : Flow End Logger" doc:id="ece82a56-9ad8-4ae6-8f5b-aa4db5246564" message='#["===== After HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
</sub-flow>
<sub-flow name="initiate-get-account" doc:id="eee0997c-7427-4292-8cdc-ea7fc5882b10" >
<logger level="DEBUG" doc:name="DEBUG : Flow Start Logger" doc:id="04e1f991-3f9a-41e1-b123-1c9ac0adbde7" message='#["===== Before HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
<ee:transform doc:name="Prepare request parameter" doc:id="64b2a22a-702d-4824-93df-54d28b6a9eae" >
<ee:variables >
<ee:set-variable variableName="requestParameter" ><![CDATA[%dw 2.0
output application/java
---
{
"method" : "GET",
"path" : '/' ++ attributes.uriParams.'accountId'
}]]></ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="common-200-requester-flow" doc:name="common-200-requester-flow"></flow>
<ee:transform doc:name="Prepare response code" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[200]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="DEBUG" doc:name="DEBUG : Flow End Logger" doc:id="ece82a56-9ad8-4ae6-8f5b-aa4db5246564" message='#["===== After HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
</sub-flow>
<sub-flow name="initiate-delete-account" doc:id="3e956c02-f292-4d26-aede-fea6f67042c7" >
<logger level="DEBUG" doc:name="DEBUG : Flow Start Logger" doc:id="04e1f991-3f9a-41e1-b123-1c9ac0adbde7" message='#["===== Before HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
<ee:transform doc:name="Prepare request parameter" doc:id="64b2a22a-702d-4824-93df-54d28b6a9eae" >
<ee:variables >
<ee:set-variable variableName="requestParameter" ><![CDATA[%dw 2.0
output application/java
---
{
"method" : "DELETE",
"path" : '/' ++ attributes.uriParams.'accountId'
}]]></ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="common-200-requester-flow" doc:name="common-200-requester-flow"></flow>
<ee:transform doc:name="Prepare response code" doc:id="72b9f60a-8a74-4eb9-9b8e-81ae265c5a0b" >
<ee:variables >
<ee:set-variable variableName="httpStatus" ><![CDATA[200]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="DEBUG" doc:name="DEBUG : Flow End Logger" doc:id="ece82a56-9ad8-4ae6-8f5b-aa4db5246564" message='#["===== After HTTP request in " ++ flow.name ++ " flow ====="]'></logger>
</sub-flow>
</mule>
Please follow here for the mobile-accounts-eapi
code.
Conclusion
This use case is to give a real-time example of an API-led design and its implementation. I tried to cover all possible best practices of API development. There could be possible improvements on the same.
Opinions expressed by DZone contributors are their own.
Comments