Domain‑Driven Design for Marketing: Abstracting Limits, Rules, Benefits, and Feedback with Node.js
This article explores how to break down the complex marketing domain into modular, atomic components—limits, rules, benefits, and feedback—using Node.js, demonstrates a flexible factory pattern for assembling activities, and provides practical advice for refactoring and product design in real‑world projects.
Preface
The author introduces the topic because a reader asked two questions about a complex backend and how to improve efficiency with design.
Domain Introduction
Case Domain
The author has about seven years of front‑end experience, especially at Ele.me, where they spent three years on marketing, collaborating with product, refactoring with R&D, and conducting offline user research. The notes are shared for marketing‑related colleagues.
Domain
Marketing.
What Is Marketing?
Marketing is often thought of as selling goods—e‑commerce, events—but that is a shallow view. A deeper understanding of the domain is required for good design.
Definition
Baike (Baidu): The process by which a company discovers or creates consumer demand, lets consumers understand the product, and then purchases it.
American Marketing Association (AMA): Marketing is the creation, communication, and delivery of value to customers and the management of customer relationships to benefit the organization and its stakeholders.
Purpose
In plain terms: customers get discounts, enterprises get profit.
More formally: through mutual exchange and commitment, relationships with consumers and other participants are built, maintained, and strengthened to achieve each party's goals.
The article will focus on how to translate marketing business requirements into software design.
Common Practices
Typical Development Mode
When receiving a business request we ask: what is the current situation? What data model should be designed? What logic is needed? How will it affect existing code structure?
This approach works when the architecture is already defined and a quick rollout is needed, but it can lead to hidden coupling.
"No Compromise" Mode
For long‑term maintainable projects, blindly adding code creates a "big stone" application that eventually becomes a burden.
Frameworks solve generic problems like design patterns, but domain‑specific design often lacks clear guidance. The author now dives into marketing‑specific abstraction.
Domain Thinking
One‑Sentence Summary
When, where, who, under what conditions, what is obtained, and the user experience.
Deep Analysis
When: time limit
Where: location or channel limit
Who: target audience
Conditions: required thresholds
What is obtained: customer rights
Experience: feedback on the marketing
First Level Abstraction
Attributes, Rules, Feedback
These three modules are loosely coupled and can be designed independently, but they are abstract and need concrete definition.
What exactly constitutes a marketing attribute?
What is a marketing rule, and does a rule count as an activity attribute?
Where does marketing feedback belong—logistics or after‑sales?
Like abstract classes, they define direction but need concrete implementation.
Second Level Abstraction
Limits, Thresholds, Benefits, Feedback
Example: Ele.me "Billion Subsidy" activity—how a user experiences the marketing:
Seen the activity (channel)
Activity runs from 2020‑08‑27 in 124 cities, can be combined with coupons and discounts (limits)
Merchant must meet a spending threshold (threshold)
User receives a monetary reduction (benefit)
User gives positive feedback (feedback)
Design Implementation
After understanding the domain, the author abstracts it into code. The following Node.js snippets illustrate the design.
Pool: Limit (pond/limit.js)
let limit = {
// time
time: null,
// address
address: null,
// people
people: null,
// channel
channel: null
}
class Limit {
// add a limit key
addLimit(key) {
limit[key] = null
}
// delete a limit key
delLimit(key) {
delete limit[key]
}
// set a specific limit value
setLimitValue(key, value) {
limit[key] = value
}
getLimit() {
let _output = {}
for (let key in limit) {
if (limit[key] !== null) _output[key] = limit[key];
}
return _output
}
}
module.exports = Limit;Pool: Rule (pond/rule.js)
let rule = {
// consumption amount
consumption: null,
// order number
orderNumber: null
}
class Rule {
addRule(key) {
rule[key] = null
}
delRule(key) {
delete rule[key]
}
setRuleValue(key, value) {
rule[key] = value
}
getRule() {
let _output = {}
for (let key in rule) {
if (rule[key] !== null) _output[key] = rule[key];
}
return _output
}
}
module.exports = Rule;Pool: Benefit (pond/benefit.js)
let benefit = {
// discount amount
money: null
}
class Benefit {
addBenefit(key) {
benefit[key] = null
}
delBenefit(key) {
delete benefit[key]
}
setBenefitValue(key, value) {
benefit[key] = value
}
getBenefit() {
let _output = {}
for (let key in benefit) {
if (benefit[key] !== null) _output[key] = benefit[key];
}
return _output
}
}
module.exports = Benefit;Pool: Feedback (pond/feedback.js)
// define data model
// let feedback = {
// // type of feedback
// type: null,
// // message content
// message: null
// }
// cache array
let feedbackArr = []
class Feedback {
// add a feedback entry
setFeedback(options) {
feedbackArr.push({
type: options.type,
message: options.message
})
}
getFeedback() {
console.log('>>>', feedbackArr)
return feedbackArr;
}
}
module.exports = Feedback;Assembly Factory (factory/index.js)
/**
* Assemble flexible marketing activities
* - Depends on upstream fragmented data
* - Combines data into new activity types
*/
const Limit = require('../pond/limit')
const Rule = require('../pond/rule')
const Benefit = require('../pond/benefit')
const Feedback = require('../pond/feedback')
let _activity = [];
class Activity {
constructor() {
this._limit = new Limit();
this._rule = new Rule();
this._benefit = new Benefit();
this._feedback = new Feedback();
this._cache = {
limit: {},
rule: {},
benefit: {},
feedback: []
}
}
getLimit() { return this._limit; }
setLimit(obj) { this._cache.limit = obj; }
getRule() { return this._rule; }
setRule(obj) { this._cache.rule = obj; }
getBenefit() { return this._benefit; }
setBenefit(obj) { this._cache.benefit = obj; }
getFeedback() { return this._feedback; }
setFeedback(key, obj) { _activity[key].feedback = _activity[key].feedback.concat(obj); }
generate() {
let _temp = Object.assign({}, this._cache);
_activity.push(_temp);
this._cache = {};
return { id: _activity.length - 1, data: _temp };
}
getActivityList() { return _activity; }
}
module.exports = Activity;Test Cases
Case 1 – Simple Full‑Reduction (2011‑11‑11, all users, spend 60 get 30 off)
// Debug run
const Activity = require('../factory/index')
let _case1 = new Activity();
// set limits
_case1.getLimit().setLimitValue('time', '2011-11-11');
_case1.getLimit().setLimitValue('people', 'all');
_case1.setLimit(_case1.getLimit().getLimit());
// set rule
_case1.getRule().setRuleValue('consumption', 60);
_case1.setRule(_case1.getRule().getRule());
// set benefit
_case1.getBenefit().setBenefitValue('money', 30);
_case1.setBenefit(_case1.getBenefit().getBenefit());
// generate activity
let _res = _case1.generate();
// simulate feedback
_case1.getFeedback().setFeedback({
type: 'message',
message: 'Wow, the activity is really cheap!'
});
_case1.setFeedback(_res.id, _case1.getFeedback().getFeedback());
console.log(_case1.getActivityList());Result
[
{
"limit": {"time":"2011:11:11","people":"all"},
"ruleL": {"consumption":60},
"benefit": {"money":30},
"feedback": [{"type":"message","message":"Wow, the activity is really cheap!"}]
}
]Case 2 – Complex B‑Station "919" Activity
// Debug run
const Activity = require('../factory/index')
let _case2 = new Activity();
// limits
_case2.getLimit().setLimitValue('time','2021-09-19');
_case2.getLimit().setLimitValue('address','Bilibili APP');
_case2.getLimit().setLimitValue('people','registered users');
_case2.getLimit().setLimitValue('channel','member purchase');
_case2.getLimit().addLimit('platform');
_case2.getLimit().setLimitValue('platform','android');
_case2.getLimit().addLimit('scene');
_case2.getLimit().setLimitValue('scene','main venue');
_case2.getLimit().addLimit('category');
_case2.getLimit().setLimitValue('category','figurines');
_case2.setLimit(_case2.getLimit().getLimit());
// rules
_case2.getRule().setRuleValue('orderNumber','10000');
_case2.getRule().addRule('sort');
_case2.getRule().setRuleValue('sort','100');
_case2.getRule().addRule('payTime');
_case2.getRule().setRuleValue('payTime','60');
_case2.getRule().setRuleValue('consumption','1000');
_case2.setRule(_case2.getRule().getRule());
// benefits
_case2.getBenefit().setBenefitValue('money',200);
_case2.getBenefit().addBenefit('coupons');
_case2.getBenefit().setBenefitValue('coupons','full 500‑100 coupon');
_case2.setBenefit(_case2.getBenefit().getBenefit());
// generate
let _res = _case2.generate();
// feedback
_case2.getFeedback().setFeedback({type:'message',message:'Wow, I got it but it was hard!'});
_case2.setFeedback(_res.id,_case2.getFeedback().getFeedback());
console.log(_case2.getActivityList());
console.log(JSON.stringify(_case2.getActivityList()));Result
[{
"limit":{...},
"rule":{...},
"benefit":{...},
"feedback":[{"type":"message","message":"Wow, I got it but it was hard!"}]
}]The examples show that even a highly complex marketing scenario can be expressed by only changing the top‑level configuration without touching the underlying design.
Although the code is written in Node.js for front‑end developers, the same abstraction can be applied to back‑end services (Java, Go, etc.), database schema design, or product planning. Product managers can use the same thinking to control the whole lifecycle—from design, launch, user feedback, to iterative improvement.
Answer to Questions
Question 1
How to approach a backend that feels overly coupled and hard‑coded?
Deeply understand the domain first, set aside the code, and clarify what the system actually does.
Identify concrete pain points (coupling, hard‑coded logic) and trace them back to root causes.
Summarize findings and draft an abstract design that addresses the domain characteristics.
Respect existing patches—they contain valuable experience that can be reused.
Brainstorm and create a prototype (the "skeleton") based on the abstract design.
Discuss the prototype with teammates to get diverse perspectives.
Iteratively flesh out the architecture, then follow normal development cycles (testing, refinement).
Question 2
How to improve efficiency and design when only basic design patterns are known?
Recognize that design is about standardizing behavior, improving understandability, and creating traceable processes.
Design can be small: optimize a performance‑critical code path, reuse existing capabilities instead of building new ones, or automate repetitive tasks such as mock data generation.
Conclusion
This is the first article on domain‑driven design for marketing. By deeply understanding the domain and then abstracting it into modular code, developers can create flexible, maintainable solutions that evolve with business needs.
Future posts will share more concrete methodologies derived from this practice.
Postscript
The approach is similar to a personal exploration of DDD. For further discussion, contact: [email protected]
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.