You are reading the documentation for Vue Storefront v2. See the latest docs.

Vue Storefront API Design Philosophy

While designing something so complex as Vue Storefront, it's vital to set up rules that will guide us when designing APIs. The purpose of those rules is to make sure that problems are solved in a similar, predictable, and consistent to understand way, which will highly contribute to the learning curve of the framework itself.

General rules

  1. We build simple, declarative, and general-purpose APIs that are not tied to implementation details or a specific solution to a problem. That way, we can ensure that our APIs will remain general-purpose and will not break on updates even if we do heavy changes in the underlying business logic.
  2. API Surface should be possibly minimal. If there is a feature that can be achieved with already existing APIs, we shouldn't add new ones just to make it simpler.
  3. Focus on good defaults and embracing convention over configuration (opens new window) paradigm. Every API should work as it is for most of the use cases and have the ability to be configured for other ones.
  4. If you introduce a new, commonly used feature (like cache), try to provide a default configuration out of the box and let users customize it, so they don't have to configure for the most common use cases, only for custom ones. This approach will drastically reduce the number of boilerplate code users has to write.
import { useProduct } from '@vue-storefront/{INTEGRATION}'
import { cacheManager } from '@vue-storefront/core'

const { search } = useProduct()

// composable is handling most common scenario under the hood
search({ id: '123'}) // under the hood adds cache tag `P123`

// you can always modify the default tags
cacheManager.setTags((tags) => {
  tags.push('C123')
  return tags
})
  1. APIs should not limit the users. If we can't fulfill all use cases with parameters, we should provide extension points so users can do this by themselves.
  2. Same code should work on every platform (excluding specific object properties and search params)
  3. Follow Domain-Driven Design principles. Try to keep everything related to a specific domain within its composable.
"Separating concerns by files is as effective as separating school friendships by desks. Concerns are “separated” when there is no coupling: changing A wouldn’t break B. Increasing the distance without addressing the coupling only makes it easier to add bugs.
~ Dan Abramov
  1. Composables should be independent and rely on each other only if they are from the same group (useUser useUserOrder). The only exception is useUser that has to be used in many other composables.
  2. If you introduce a new feature shared across all/many composables (like Logging/cache) users should be able to configure this feature from core nuxt module.
// every function in composables is using logger
const removeFromCart = async (product: CART_ITEM, customQuery?: CustomQuery) => {
  Logger.debug('userCart.removeFromCart', { product })
  loading.value = true;
  const updatedCart = await factoryParams.removeFromCart(
    {
      currentCart: cart.value,
      product
    },
    customQuery
  );
  cart.value = updatedCart;
  loading.value = false;
};
// there is only one palce where we're configuring the logger/cache - core
['@vue-storefront/nuxt', {
  coreDevelopment: true,
  logger: customLogger
}]
  1. If a feature is platform-specific and not shared across the whole application, provide integration through its config/nuxt module.
  2. Provide a core interface for every feature, no matter if its paid or not (implementation can be paid, the way of implementing this feature by the user has to be always provided)

Composables

We try to cover each subdomain of the eCommerce domain with a dedicated composable. For example, we have a composable for Users Management domain, inventory domain, product catalog domain, etc. If you have to add a new feature, always think about the business domain it correlates to and, based on that, decide if it should be a new composable or an existing one.

If composables share the same category/prefix, it means that they most likely also share the same context, e.g., useUserOrder useUserShipping useUserBilling are all sub-composables of useUser, and their content depends on this composable.

Each composable usually has three pieces:

  • main data object (e.g. products)
  • supportive data object/s (e.g. loading, error)
  • search/load function (eg search)
const { search, products, loading, erro } = useProduct()

search or load

As a rule of thumb use

  • search when you have to pass some search parameters (e.g., in products search)
  • load when you just have to load some content based on cookies/local storage etc. (e.g., cart load)