Autolyse convert to service
Overview
Convert a Controller action to an Autolyse gRPC service. See .agent/rules/autolyse-migration.mdc for complete patterns and .agent/rules/autolyse-quickref.mdc for quick reference.
Steps
Gather & validate inputs
- Service name (e.g., "MyFeature", "CampaignData")
- Domain/package (e.g., "campaign", "audience", "reporting")
- Version (default: "v1")
- Jira ticket (ABC-1234 or full URL)
- Check working tree: Verify clean state with no uncommitted changes
- If uncommitted changes exist: STOP and ask user to commit/stash them first
- Validate: Service doesn't already exist
Create proto file
- Path:
proto/mailchimp/[domain]/[version]/[service].proto - Must include:
syntax = "proto3";package mailchimp.[domain].[version];import "mailchimp/options.proto";- Service with
option (mailchimp.options.js_callable) = true;
- Define request/response messages
- Define service RPCs
- See
.agent/rules/autolyse-migration.mdcfor proto templates
- Path:
Generate code
- Run:
script/generate-twirp proto/mailchimp/[domain]/[version]/[service].proto - This creates:
- PHP:
app/lib-grpc/Mailchimp/[Domain]/[Version]/[Service]*.php - TypeScript:
web/js/src/@mc/autolyse/[Domain]/[Version]/[Service].ts
- PHP:
- Verify generated files exist
- Run:
Create service implementation
- Path:
app/lib/MC/[Domain]/[Service]DataProvider.php - Must implement:
[Service]AutolyseInterface - Implement all RPC methods from proto
- Add error handling with
TwirpError::newError() - Add logging with
Avesta_Log_Logger::get() - See
.agent/rules/autolyse-quickref.mdcfor implementation template
- Path:
Register service in Services.php
- File:
app/lib/Autolyse/Services.php - Add registration method:
private static function register[ServiceName]Service(Server $server): void - Include required middleware:
Convert503(REQUIRED)SanitizeErrors(\MC::config()->debug)(REQUIRED)ReportErrors(recommended)RequireUser(if authentication needed)ValidateCSRF(if state-changing operations)
- Call registration in
build()method - See
.agent/rules/autolyse-quickref.mdcfor registration templates
- File:
Create PHPUnit tests
- Path:
tests_phpunit/unit/MC/[Domain]/[Service]DataProviderTest.php - Test each RPC method
- Test error cases
- Test validation logic
- Path:
Check and update Jira ticket status
- Apply Jira status management (see
.agent/rules/jira-status-management.mdc) - Automatically move ticket to "In Progress" if not already there
- Gracefully handle MCP unavailability (don't block service creation)
- Show ticket status and any transitions performed
- Apply Jira status management (see
Git operations (batched) - Request
["git_write"]permissions upfront- Create branch:
{ticket-prefix}-{ticket-num}-autolyse-{service-name}- Example:
XP-1234-autolyse-campaign-service
- Example:
- Stage ALL new files:
git add -A- Proto file
- Generated PHP/TypeScript files
- Implementation class
- Tests
- Services.php changes
- Verify ONLY new service files are staged (no unrelated changes)
- Commit:
"[{TICKET}] Add {ServiceName} Autolyse service" - Push to origin
- Create branch:
Create example TypeScript usage
- Show how to import and use the client
- Include error handling
- Add to PR description
Create PR
- Read
.github/pull_request_template.mdfor template structure - Create PR:
gh pr create --title "[{TICKET}] Add {ServiceName} Autolyse service" --body "{populated_template}" - Populate ALL template sections:
- Background context: Why this service is needed, what problem it solves
- Change summary: Proto definition, implementation details, new RPCs
- Steps to test: How to test each RPC method, expected responses
- Risk mitigation table:
- 🚩 Flag/Experiment name: Feature flag if gated (or "N/A - no flag")
- 🌊 Splatter zone: Areas affected by new service
- 👀 Monitoring: Service logs, error tracking
- 💬 Slack Channel: {team_channel}
- 🎟️ Jira ticket: https://jira.your-company.com/browse/{TICKET}
- Include TypeScript usage example in description
- Apply label:
skill-used - Submit as draft PR initially
Validation Rules
- ✅ Proto must have
option (mailchimp.options.js_callable) = true; - ✅ All RPC methods must have corresponding implementation
- ✅ Must include
Convert503andSanitizeErrorsmiddleware - ✅ Service must be registered in
Services.phpand called inbuild() - ✅ Tests must cover all RPC methods
- ❌ NEVER edit generated files in
app/lib-grpc/ - ❌ NEVER reuse proto field numbers
Proto Template
syntax = "proto3";
package mailchimp.[domain].[version];
import "mailchimp/options.proto";
message GetDataRequest {
string id = 1;
}
message GetDataResponse {
repeated Item items = 1;
int32 total_count = 2;
}
message Item {
string id = 1;
string name = 2;
}
service [ServiceName]Service {
option (mailchimp.options.js_callable) = true;
rpc GetData(GetDataRequest) returns (GetDataResponse);
}
Implementation Template
<?php
namespace MC\[Domain];
use Mailchimp\[Domain]\[Version]\[Service]AutolyseInterface;
use Mailchimp\[Domain]\[Version]\GetDataRequest;
use Mailchimp\[Domain]\[Version]\GetDataResponse;
use Mailchimp\[Domain]\[Version]\TwirpError;
use Twirp\ErrorCode;
class [Service]DataProvider implements [Service]AutolyseInterface
{
public function GetData(array $ctx, GetDataRequest $req): GetDataResponse
{
if (empty($req->getId())) {
throw TwirpError::newError(ErrorCode::InvalidArgument, 'id is required');
}
$user = \MC::user();
// Business logic...
$response = new GetDataResponse();
$response->setTotalCount(0);
return $response;
}
}
TypeScript Usage Example
import { [ServiceName]ServiceClient } from '@mc/autolyse/[Domain]/[Version]/[ServiceName]Service';
const client = new [ServiceName]ServiceClient();
try {
const response = await client.GetData({ id: '123' });
console.log(response.items);
} catch (error) {
console.error(error.code, error.msg);
}
Checklist
Before submitting PR, verify:
- Proto file created with
js_callableoption - Code generated with
script/generate-twirp - Service implementation class created
- All RPC methods implemented
- Error handling added
- Service registered in
Services.php - Registration called in
build()method - PHPUnit tests created
- All tests pass:
devenv test tests_phpunit/unit/MC/[Domain]/ - TypeScript client imports successfully
- Branch created and pushed
- PR created with complete description
See Also
.agent/rules/autolyse-migration.mdc- Complete migration guide.agent/rules/autolyse-quickref.mdc- Quick reference cardproto/mailchimp/autolyse/example.proto- Example proto fileapp/lib/MC/Campaign/CampaignDataProvider.php- Example implementation