Ecommerce integration patterns and decomposer/composer
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Decomposers transform external platform data → Violet canonical models.
public interface Decomposer<P, V> {
V decompose(P platformEntity);
List<V> decomposeList(List<P> platformEntities);
}
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;
};
}
}
Composers transform Violet canonical models → external platform formats.
public interface Composer<V, P> {
P compose(V violetEntity);
List<P> composeList(List<V> violetEntities);
}
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";
};
}
}
@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();
}
@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");
}
Every integration must document status mappings:
| 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 |