Introduction
In the previous article, we discussed the architecture of SAP Commerce OCC Web Services.
We saw the typical request flow:
Client ↓ OCC Controller ↓ Facade ↓ Service ↓ DAO ↓ FlexibleSearch ↓ Database
Today, instead of only discussing the architecture, we are going to build a custom OCC API.
We will create an API that retrieves a product by product code.
Our final API will look like:
GET /occ/v2/electronics/customproducts/P100
and return:
{ "code": "P100", "name": "Laptop", "description": "Business Laptop" }
This is the kind of practical knowledge that is especially useful when working on real SAP Commerce projects.
SAP's current documentation recommends using OCC Extensions to extend OCC functionality. OCC extensions contain REST controllers and related classes in /src, while their web Spring configuration is placed under /resources/occ/v2/<extension>/web/spring.
1. What Are We Going to Build?
Our custom API will follow this architecture:
REST CLIENT | ↓ CustomProductController | ↓ CustomProductFacade | ↓ CustomProductService | ↓ CustomProductDao | ↓ FlexibleSearch | ↓ Database
Response:
Database ↓ ProductModel ↓ Service ↓ Facade ↓ Converter ↓ ProductData ↓ ProductWsDTO ↓ JSON
2. Prerequisites
Before implementing the API, you should have:
- SAP Commerce installed
- A custom SAP Commerce extension
- Java knowledge
- Spring knowledge
- Basic FlexibleSearch knowledge
- Basic OCC knowledge
- Postman or another REST client
3. Create an OCC Extension
For this example, let's assume our extension is:
customocc
SAP's OCC extension naming convention requires the extension name to end with occ.
For example:
xyzocc customocc mycompanyocc
SAP documents generating OCC extensions using the yocc extension template and adding the resulting extension to localextensions.xml.
4. Add the Extension to localextensions.xml
Open:
config/localextensions.xml
Add:
<extension name="customocc"/>
For example:
<extensions> <extension name="commercewebservices"/> <extension name="customcore"/> <extension name="customfacades"/> <extension name="customocc"/> </extensions>
The exact dependency structure depends on your project.
5. OCC Extension Directory Structure
A typical OCC extension looks like:
customocc │ ├── extensioninfo.xml │ ├── resources │ │ │ └── occ │ └── v2 │ └── customocc │ │ │ └── web │ └── spring │ └── customocc-web-spring.xml │ └── src │ └── com └── mycompany └── customocc │ └── controllers
This structure is important.
SAP's documentation specifies that the Spring configuration should be under:
/resources/occ/v2/<extension_name>/web/spring
and the configuration filename should end with:
-web-spring.xml
6. Create the DAO
Our DAO will retrieve a product using FlexibleSearch.
Create:
public interface CustomProductDao { ProductModel findProductByCode(String code); }
Implementation:
public class DefaultCustomProductDao implements CustomProductDao { @Resource private FlexibleSearchService flexibleSearchService; @Override public ProductModel findProductByCode( final String code) { final String query = "SELECT {p.pk} " + "FROM {Product AS p} " + "WHERE {p.code} = ?code"; final FlexibleSearchQuery searchQuery = new FlexibleSearchQuery(query); searchQuery.addQueryParameter( "code", code); final SearchResult<ProductModel> result = flexibleSearchService.search(searchQuery); return result.getResult() .stream() .findFirst() .orElse(null); } }
The DAO has one responsibility:
Retrieve the data.
It should not contain presentation logic.
7. Create the Service
Now create the service interface:
public interface CustomProductService { ProductModel getProductByCode(String code); }
Implementation:
public class DefaultCustomProductService implements CustomProductService { @Resource private CustomProductDao customProductDao; @Override public ProductModel getProductByCode( final String code) { return customProductDao .findProductByCode(code); } }
The architecture is now:
Service ↓ DAO ↓ FlexibleSearch
8. Why Do We Need the Service Layer?
A common mistake is:
Controller ↓ DAO
Instead, we use:
Controller ↓ Facade ↓ Service ↓ DAO
Why?
Because business logic belongs in the Service Layer.
For example, tomorrow we might introduce:
Only APPROVED products Only products from a specific catalog Customer-specific pricing Stock validation Product visibility rules
That logic can be handled by the Service Layer.
9. Create the Data Object
Now we need an object to carry product information.
For example:
public class CustomProductData { private String code; private String name; private String description; public String getCode() { return code; } public void setCode(final String code) { this.code = code; } public String getName() { return name; } public void setName(final String name) { this.name = name; } public String getDescription() { return description; } public void setDescription( final String description) { this.description = description; } }
This object is different from:
ProductModel
The model represents the SAP Commerce item.
The Data object represents the information we want to expose through the application layer.
10. Create the Converter
SAP Commerce uses converters and populators extensively to construct Data objects from Models or other service-layer objects. SAP's documentation recommends Spring-configured converters and keeping conversion logic inside populators.
Create:
public class CustomProductPopulator implements Populator<ProductModel, CustomProductData> { @Override public void populate( final ProductModel source, final CustomProductData target) { target.setCode(source.getCode()); target.setName(source.getName()); target.setDescription( source.getDescription()); } }
11. Why Use a Populator?
Suppose tomorrow we want to add:
Price Stock Images Categories Classification Reviews Promotions
Instead of putting everything into one huge class, we can create:
CustomProductBasicPopulator CustomProductPricePopulator CustomProductStockPopulator CustomProductImagePopulator
This keeps the conversion logic modular.
SAP specifically describes populators as a pipeline of population tasks and notes that configurable populators can help avoid unnecessary conversion and reduce performance/bandwidth costs.
12. Create the Facade
Create:
public interface CustomProductFacade { CustomProductData getProductByCode( String code); }
Implementation:
public class DefaultCustomProductFacade implements CustomProductFacade { @Resource private CustomProductService customProductService; @Resource private Converter<ProductModel, CustomProductData> customProductConverter; @Override public CustomProductData getProductByCode( final String code) { final ProductModel product = customProductService .getProductByCode(code); if (product == null) { return null; } return customProductConverter .convert(product); } }
Now:
Facade ↓ Service ↓ DAO
13. Create the WsDTO
Now create the web-service DTO.
public class CustomProductWsDTO { private String code; private String name; private String description; public String getCode() { return code; } public void setCode(final String code) { this.code = code; } public String getName() { return name; } public void setName(final String name) { this.name = name; } public String getDescription() { return description; } public void setDescription( final String description) { this.description = description; } }
The purpose is to keep the API contract separate from internal SAP Commerce models.
SAP's OCC implementation documentation identifies WsDTO as the data layer used by the OCC REST API.
14. Create the OCC Controller
Now we get to the most important part.
Create:
@Controller @RequestMapping( value = "/{baseSiteId}/customproducts") public class CustomProductController { @Resource private CustomProductFacade customProductFacade; @RequestMapping( value = "/{code}", method = RequestMethod.GET) @ResponseBody public CustomProductWsDTO getProduct( @PathVariable final String code) { final CustomProductData productData = customProductFacade .getProductByCode(code); return convertToWsDTO(productData); } protected CustomProductWsDTO convertToWsDTO( final CustomProductData data) { final CustomProductWsDTO dto = new CustomProductWsDTO(); dto.setCode(data.getCode()); dto.setName(data.getName()); dto.setDescription( data.getDescription()); return dto; } }
SAP's official OCC extension example follows this general pattern: a controller is created under /src, mapped using Spring MVC annotations, and returns a WsDTO.
15. Our API URL
The controller:
@RequestMapping( value = "/{baseSiteId}/customproducts")
combined with:
@RequestMapping( value = "/{code}", method = RequestMethod.GET)
gives:
/occ/v2/{baseSiteId}/customproducts/{code}
For example:
/occ/v2/electronics/customproducts/P100
SAP documents /occ/v2 as the standard web root for commercewebservices.
16. Configure the Spring Beans
Now we need to tell Spring about our beans.
Create:
customocc-web-spring.xml
under:
resources/occ/v2/customocc/web/spring/
Example:
<bean id="customProductDao" class="com.mycompany.customocc.dao.impl.DefaultCustomProductDao"> </bean> <bean id="customProductService" class="com.mycompany.customocc.service.impl.DefaultCustomProductService"> <property name="customProductDao" ref="customProductDao"/> </bean> <bean id="customProductPopulator" class="com.mycompany.customocc.populator.CustomProductPopulator"> </bean> <bean id="customProductConverter" class="de.hybris.platform.servicelayer.dto.converter.impl.AbstractPopulatingConverter"> <property name="targetClass" value="com.mycompany.customocc.data.CustomProductData"/> <property name="populators"> <list> <ref bean="customProductPopulator"/> </list> </property> </bean> <bean id="customProductFacade" class="com.mycompany.customocc.facade.impl.DefaultCustomProductFacade"> <property name="customProductService" ref="customProductService"/> <property name="customProductConverter" ref="customProductConverter"/> </bean>
Note: In a real project, you would normally separate beans into the appropriate core/facades/OCC extensions rather than placing every layer inside the OCC extension. This example keeps the article easy to understand.
17. Build the Project
After creating the extension and configuration:
ant clean all
Then restart the SAP Commerce server.
For a local environment, you may use your normal SAP Commerce startup process.
18. Test the API Using Postman
Open Postman.
Select:
GET
Enter:
https://localhost:9002/occ/v2/electronics/customproducts/P100
Depending on your local SSL configuration, you may use:
http://localhost:9001/occ/v2/electronics/customproducts/P100
Use the port configured in your environment.
19. Authentication
If the endpoint is secured, obtain an appropriate OAuth access token and add:
Authorization: Bearer <access-token>
SAP Commerce OCC uses configurable Spring Security mechanisms for securing OCC calls.
20. Expected Response
If product P100 exists:
{ "code": "P100", "name": "Laptop", "description": "Business Laptop" }
The complete flow is:
GET /occ/v2/electronics/customproducts/P100 | ↓ CustomProductController | ↓ CustomProductFacade | ↓ CustomProductService | ↓ CustomProductDao | ↓ FlexibleSearch | ↓ ProductModel | ↓ Converter | ↓ ProductData | ↓ WsDTO | ↓ JSON
21. Handling Product Not Found
The current example returns null.
In a production API, this is not ideal.
Instead, we should throw a meaningful exception.
For example:
if (product == null) { throw new UnknownIdentifierException( "Product not found: " + code); }
Then the OCC error-handling mechanism can generate an appropriate error response.
For example:
{ "errors": [ { "type": "UnknownIdentifierError", "message": "Product not found: P999" } ] }
SAP Commerce provides an OCC error-response mechanism specifically for converting exceptions into REST responses.
22. Bad Request vs Not Found
A good API should distinguish errors.
Invalid request
400 Bad Request
Authentication failure
401 Unauthorized
Access denied
403 Forbidden
Resource doesn't exist
404 Not Found
Server error
500 Internal Server Error
23. Don't Put FlexibleSearch in Controller
Avoid:
@Controller public class ProductController { @RequestMapping(...) public ProductWsDTO getProduct(...) { // FlexibleSearch here } }
This creates tight coupling.
Instead:
Controller ↓ Facade ↓ Service ↓ DAO
This is easier to:
- Test
- Maintain
- Extend
- Debug
- Reuse
24. Don't Return ProductModel
Avoid:
public ProductModel getProduct(...)
from the API.
Prefer:
ProductModel ↓ ProductData ↓ ProductWsDTO
This prevents internal persistence structures from becoming your public API contract.
25. OCC Extension vs OCC AddOn
This is an important topic for experienced SAP Commerce developers.
Modern SAP Commerce provides OCC Extensions.
An OCC extension:
Depends on commercewebservices
and is automatically imported into the Commerce Web Services Spring context.
SAP explicitly documents that OCC extensions are not web extensions and don't require the traditional AddOn installation process.
Architecture:
commercewebservices ↑ | customocc
Whereas the older AddOn approach had a different dependency/installation model.
For new development, you should understand the OCC Extension architecture and also recognize older AddOn-based implementations in legacy projects.
26. Why OCC Extensions Are Better for New Development
With an OCC extension:
customocc | ├── Controllers ├── Spring beans ├── DTOs └── Messages
There is no need to copy controller files into the web application.
SAP documents that Commerce Web Services imports OCC extension Spring configurations automatically using the OCC extension path convention.
27. Performance Considerations
When designing an OCC API, don't only focus on making the endpoint work.
You should also consider performance.
Avoid unnecessary database queries
Bad:
Product ↓ Price query ↓ Stock query ↓ Category query ↓ Image query ↓ Review query
for every request.
Instead, carefully design your service and converter/populator pipeline.
Avoid huge responses
Don't return:
100+ fields
when the client needs:
5 fields
SAP specifically highlights configurable populators as a way to avoid unnecessary conversion and reduce performance/bandwidth costs.
28. Pagination
Never return thousands of products in one API response.
Instead:
GET /occ/v2/electronics/products? pageSize=20& currentPage=0
Conceptually:
Page 0 → 20 products Page 1 → next 20 Page 2 → next 20
Pagination is especially important for:
- Product search
- Orders
- Customers
- Categories
- Saved carts
29. API Versioning
Imagine your API is:
/occ/v2/electronics/customproducts/P100
Later you make a breaking change.
You should think carefully about API compatibility rather than unexpectedly changing the existing response contract.
Versioning provides a way to evolve APIs safely.
30. Testing Strategy
For a production OCC API, don't test only through Postman.
You should have:
Unit tests
Test:
Facade Service Populator Converter
Integration tests
Test:
DAO FlexibleSearch Spring configuration
OCC/API tests
Test:
HTTP request Authentication Response HTTP status Error response
31. Interview Question: Explain Your Custom OCC API
If an interviewer asks:
"Explain how you created a custom OCC API in SAP Commerce."
A strong answer would be:
"I created a dedicated OCC extension and added a Spring MVC controller. The controller accepts the REST request and delegates to a facade. The facade calls the service layer, which contains the business logic and uses a DAO for data retrieval. The DAO executes FlexibleSearch and returns the model. I then use a converter and populator to transform the model into a Data object, which is exposed through a WsDTO. Finally, OCC serializes the WsDTO into JSON."
That's a much stronger answer than:
"I created a controller and exposed a REST endpoint."
32. Interview Question: Why Facade Between Controller and Service?
Answer:
The facade provides a presentation-oriented API and hides underlying service complexity.
It also allows us to:
- Coordinate multiple services
- Convert models to Data objects
- Prepare data for presentation
- Keep controllers thin
33. Interview Question: Why Use Populators?
Answer:
Populators separate individual conversion responsibilities.
For example:
ProductBasicPopulator ProductPricePopulator ProductStockPopulator ProductImagePopulator
They can be composed into a converter pipeline.
This improves modularity and makes it easier to add or remove data population logic. SAP's documentation specifically recommends keeping conversion logic in populators rather than calling populators directly from application code.
34. Interview Question: What Happens When OCC Receives a Request?
A good answer:
HTTP Request ↓ Spring MVC ↓ OCC Controller ↓ Facade ↓ Service ↓ DAO ↓ Database
Then:
Database ↓ Model ↓ Converter ↓ Data ↓ WsDTO ↓ HTTP Response
35. Complete Project Structure
A practical project could eventually look like:
customcore │ ├── dao ├── service └── model customfacades │ ├── facade ├── data ├── converter └── populator customocc │ ├── controllers ├── dto └── resources └── occ └── v2 └── customocc └── web └── spring └── customocc-web-spring.xml
This separation is cleaner for a real enterprise project.
36. Final Architecture
Remember this:
CLIENT | | HTTP ↓ ┌───────────────┐ │ OCC Controller│ └───────┬───────┘ | ↓ ┌───────────────┐ │ Facade │ └───────┬───────┘ | ↓ ┌───────────────┐ │ Service │ └───────┬───────┘ | ↓ ┌───────────────┐ │ DAO │ └───────┬───────┘ | ↓ FlexibleSearch | ↓ Database | ↓ ProductModel | ↓ Converter | ↓ Populator | ↓ ProductData | ↓ WsDTO | ↓ JSON | ↓ CLIENT
Conclusion
Creating a custom OCC API is one of the most useful skills for a SAP Commerce developer.
The most important concepts to remember are:
OCC Extension ↓ Controller ↓ Facade ↓ Service ↓ DAO ↓ FlexibleSearch ↓ Model ↓ Converter ↓ Populator ↓ Data ↓ WsDTO ↓ JSON
The key principle is separation of responsibilities.
The Controller handles HTTP.
The Facade handles presentation-oriented orchestration.
The Service handles business logic.
The DAO handles data access.
The Converter/Populator handles object transformation.
The WsDTO defines what the API exposes.
This architecture makes SAP Commerce applications easier to maintain, test, scale and extend.
No comments:
Post a Comment