DEV Community

Cover image for Stop Parsing JSON Between Services
Yuval Saraf
Yuval Saraf

Posted on

Stop Parsing JSON Between Services

A Better Way for Inter-Service Communication

The Common Approach: The Ball Game

Imagine building a system where different services pass a ball around, incrementing its value each time. Here's how it typically looks:

// Node.js service
import Redis from 'redis';

const redis = Redis.createClient();
await redis.connect();

// Subscribe to receive the ball
redis.subscribe('ball:node', async (message) => {
  try {
    const data = JSON.parse(message);
    if (typeof data.value !== 'number') {
      console.error('Invalid ball value');
      return;
    }
    const newValue = data.value + 1;
    await redis.publish('ball:python', JSON.stringify({ value: newValue }));
  } catch (e) {
    console.error('Failed to parse ball data', e);
  }
});
Enter fullscreen mode Exit fullscreen mode
# Python service
import redis
import json

r = redis.Redis()
pubsub = r.pubsub()

def handle_message(message):
    try:
        data = json.loads(message['data'])
        if not isinstance(data.get('value'), (int, float)):
            print('Invalid ball value')
            return
        new_value = data['value'] + 1
        r.publish('ball:rust', json.dumps({'value': new_value}))
    except json.JSONDecodeError:
        print('Failed to parse ball data')
    except KeyError:
        print('Missing value in ball data')

pubsub.subscribe('ball:python')
for message in pubsub.listen():
    if message['type'] == 'message':
        handle_message(message)
Enter fullscreen mode Exit fullscreen mode

The problems here are clear:

  • Manual JSON parsing and error handling everywhere
  • No type safety between services
  • Channel names need to be manually synchronized
  • Different error handling patterns per language
  • Easy to make mistakes when modifying the data structure

A Better Way: Schema-First Development with Memorix

First, let's set up Memorix:

# Install CLI
brew tap uvop/memorix
brew install memorix

# Install client for your language
npx jsr add @memorix/client-redis    # Node.js/Bun/Deno
pip install memorix-client-redis      # Python
cargo add memorix-client-redis        # Rust
Enter fullscreen mode Exit fullscreen mode

Now, define your schema once (schema.memorix):

Config {
  export: {
    engine: Redis(env(REDIS_URL))
    files: [
      {
        language: typescript
        path: "node/src/schema.generated.ts"
      }
      {
        language: python
        path: "python/src/schema_generated.py"
      }
      {
        language: rust
        path: "rust/src/schema_generated.rs"
      }
    ]
  }
}

Enum {
  System {
    NODE
    PYTHON
    RUST
  }
}

Task {
  pass_ball: {
    key: System
    payload: u64
  }
}
Enter fullscreen mode Exit fullscreen mode

Generate the type-safe clients:

memorix codegen ./schema.memorix
Enter fullscreen mode Exit fullscreen mode

Look how clean the services become:

// Node service
import * as mx from "./schema.generated.ts";

const memorix = new mx.Memorix();
await memorix.connect();

const gen = await memorix.task.pass_ball.dequeue(mx.System.NODE);
for await (const ball of gen.asyncIterator) {
  const biggerBall = ball + 1;
  await memorix.task.pass_ball.enqueue(mx.System.PYTHON, biggerBall);
}
Enter fullscreen mode Exit fullscreen mode
# Python service
from schema_generated import Memorix, System

memorix = Memorix()
memorix.connect()

for ball in memorix.task.pass_ball.dequeue(System.PYTHON):
    bigger_ball = ball + 1
    memorix.task.pass_ball.enqueue(System.RUST, bigger_ball)
Enter fullscreen mode Exit fullscreen mode

The benefits are immediately clear:

  • No manual JSON parsing or error handling
  • Full type safety and IDE autocomplete in every language
  • Consistent API across languages and platforms (Redis, Kafka, etc.)
  • Modern language features like async generators
  • Compile-time errors catch mistakes early
  • Changed from publish/subscribe to queue-based messaging for easy scaling across multiple instances

Other Memorix features

Rich Data Typing:

Task {
    processUser: {
        payload: { # Inline type
          id: u64
          authType: UserType # Enums
          avatar_url: string? # Optional
          data: UserData # Sharing complex typing
        }
    }
}

Enum {
  UserAuthType {
    EMAIL
    GOOGLE
    GITHUB
  }
}

Type {
  UserData: {
    is_admin: boolean
    preferences: [
      {
        preference_id: string
        value: boolean
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Caching with TTL:

Cache {
    userSession: {
        key: string
        payload: UserSession
        ttl: "3600"  # Expires in 1 hour
    }
}
Enter fullscreen mode Exit fullscreen mode

Pub/Sub for Broadcast Messages:

PubSub {
    userLoggedIn: {
        payload: {
            userId: string
            timestamp: u64
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Task Queues with Different Strategies:

Task {
    processImage: {
        payload: ImageData
        queue_type: "lifo"  # Last in, first out
    }
}
Enter fullscreen mode Exit fullscreen mode

Scalability with Ease:

  • Import/export schema files
  • Private/public APIs
  • Namespacing
  • Environment variables support
  • Cross-service type sharing

Conclusion

The ball game example shows how Memorix transforms messy inter-service communication into clean, type-safe code. Instead of worrying about JSON parsing and message formats, you can focus on building features.

Ready to try it? Check out the complete example on GitHub or dive into the documentation.

Top comments (3)

Collapse
 
omerman profile image
Omer

I can’t wait for systems to start adopting this kind of concept, loved the Rust touch πŸ‘ How much work is it to support more languages?

Collapse
 
unimonkiez profile image
Yuval Saraf

Actually not that much, since the compiler is already built for it (also in Rust πŸ¦€).
I'm in the process of adding support for Golang.
Maybe C# next?

Collapse
 
omerman profile image
Omer

Exactly the languages I had in mind πŸ˜‚
Sounds great, Ill spread the word