Backend Development 24 min read

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.

ByteFE
ByteFE
ByteFE
Domain‑Driven Design for Marketing: Abstracting Limits, Rules, Benefits, and Feedback with Node.js

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]

Backendsoftware architectureDomain-Driven DesignrefactoringMarketingnodejs
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.