Install
npx skillscat add violetio/violet-ai-plugins/ecom-integrations Install via the SkillsCat registry.
About this skill
This skill provides Decomposer and Composer patterns for converting data between external ecommerce platforms and Violet's canonical data models. It solves the problem of platform-specific data inconsistencies by enforcing pure, null-safe transformations with status mapping and field normalization. Developers should use it when building integrations with ecommerce platforms like Shopify to standardize product, order, or customer data exchange.
SKILL.md
Decomposer/Composer Patterns
Decomposer Pattern
Decomposers transform external platform data → Violet canonical models.
Interface
public interface Decomposer<P, V> {
V decompose(P platformEntity);
List<V> decomposeList(List<P> platformEntities);
}Example: Shopify Product Decomposer
public class ShopifyProductDecomposer implements Decomposer<ShopifyProduct, Offer> {
public static Offer decompose(ShopifyProduct shopifyProduct) {
Offer offer = new Offer();
offer.setExternalId(String.valueOf(shopifyProduct.getId()));
offer.setName(shopifyProduct.getTitle());
offer.setDescription(shopifyProduct.getBodyHtml());
offer.setVendor(shopifyProduct.getVendor());
offer.setSkus(decomposeVariants(shopifyProduct.getVariants()));
offer.setImages(decomposeImages(shopifyProduct.getImages()));
offer.setTags(parseTags(shopifyProduct.getTags()));
offer.setStatus(mapStatus(shopifyProduct.getStatus()));
return offer;
}
private static OfferStatus mapStatus(String shopifyStatus) {
return switch (shopifyStatus) {
case "active" -> OfferStatus.AVAILABLE;
case "draft" -> OfferStatus.DRAFT;
case "archived" -> OfferStatus.UNAVAILABLE;
default -> OfferStatus.UNKNOWN;
};
}
}Key Principles
- Pure functions - No side effects, static methods
- Null safety - Check every field before accessing
- Status mapping - Translate platform statuses to Violet statuses
- Field normalization - Convert to consistent formats (currency, units)
Composer Pattern
Composers transform Violet canonical models → external platform formats.
Interface
public interface Composer<V, P> {
P compose(V violetEntity);
List<P> composeList(List<V> violetEntities);
}Example: Shopify Order Composer
public class ShopifyOrderComposer implements Composer<Order, ShopifyOrderInput> {
public static ShopifyOrderInput compose(Order violetOrder) {
ShopifyOrderInput input = new ShopifyOrderInput();
input.setEmail(violetOrder.getCustomer().getEmail());
input.setLineItems(composeLineItems(violetOrder.getItems()));
input.setShippingAddress(composeAddress(violetOrder.getShippingAddress()));
input.setBillingAddress(composeAddress(violetOrder.getBillingAddress()));
input.setFinancialStatus(mapFinancialStatus(violetOrder.getPaymentStatus()));
input.setFulfillmentStatus(mapFulfillmentStatus(violetOrder.getFulfillmentStatus()));
return input;
}
private static String mapFinancialStatus(PaymentStatus status) {
return switch (status) {
case PAID -> "paid";
case PENDING -> "pending";
case REFUNDED -> "refunded";
case PARTIALLY_REFUNDED -> "partially_refunded";
default -> "pending";
};
}
}Key Principles
- Idempotent - Same input always produces same output
- Complete - Include all required platform fields
- Validated - Output should pass platform validation
- Documented - Every field mapping documented
Testing Patterns
Decomposer Tests
@Test
void decompose_shouldMapAllFields() {
ShopifyProduct input = createTestProduct();
Offer result = ShopifyProductDecomposer.decompose(input);
assertThat(result.getExternalId()).isEqualTo("123");
assertThat(result.getName()).isEqualTo("Test Product");
assertThat(result.getStatus()).isEqualTo(OfferStatus.AVAILABLE);
}
@Test
void decompose_shouldHandleNullFields() {
ShopifyProduct input = new ShopifyProduct();
input.setId(123L);
// Other fields null
Offer result = ShopifyProductDecomposer.decompose(input);
assertThat(result.getExternalId()).isEqualTo("123");
assertThat(result.getName()).isNull();
}Composer Tests
@Test
void compose_shouldProduceValidPlatformInput() {
Order violetOrder = createTestOrder();
ShopifyOrderInput result = ShopifyOrderComposer.compose(violetOrder);
assertThat(result.getEmail()).isNotNull();
assertThat(result.getLineItems()).isNotEmpty();
}
@Test
void compose_shouldMapStatusCorrectly() {
Order order = new Order();
order.setPaymentStatus(PaymentStatus.PAID);
ShopifyOrderInput result = ShopifyOrderComposer.compose(order);
assertThat(result.getFinancialStatus()).isEqualTo("paid");
}Status Mapping Tables
Every integration must document status mappings:
Order Status Example
| Violet Status | Shopify | BigCommerce | WooCommerce |
|---|---|---|---|
| PENDING | unfulfilled | Awaiting Fulfillment | pending |
| PROCESSING | in_progress | Awaiting Shipment | processing |
| SHIPPED | fulfilled | Shipped | completed |
| DELIVERED | fulfilled | Delivered | completed |
| CANCELLED | cancelled | Cancelled | cancelled |