mirror of
https://github.com/hanzoai/insights.git
synced 2026-11-03 00:52:32 +00:00
PersonHog was the last of it, and it was not only a name. The package is personinsights and the types in it are already Person* -- the shipped descriptor under personinsights_client/proto/generated says PersonService and the tests say TestFakePersonClient -- but the .proto still declared PersonHogService. A client dialling one service name and a server built from a .proto declaring another is not a naming problem, it is a wire that does not connect; the only reason nobody hit it is that none of these services runs here yet. Five service declarations now say what the generated client has said all along. StreamHog was a second fictional company, invented so a Stripe mock had someone to bill. This fork already has Hedgebox, in five hundred fixtures, so the mock bills Hedgebox and there is one made-up company again instead of two. REVIEW_HOG was an enum member in three enums that had to agree, whose value has been `review` since the app was renamed. Only the member said otherwise. Every rule is in bin/debrand and every spelling is refused there, so a fresh upstream import lands on these names and none of them can come back. Two are named as outstanding rather than refused, with the reason each is still sayable: hogdesk survives inside applied migrations' choices lists, where Django compares it in Python and never writes it -- the member is Desk = "desk" and all three tables that store it hold no rows; sherlockhog keys a service in another repository that still calls itself that, and renaming our half would classify its traffic as a product that does not exist. Co-authored-by: Hanzo Dev <[email protected]>
4.2 KiB
4.2 KiB
Proto conventions and worked example
Message conventions
int64for IDs, timestamps (Unix millis), versionsbytesfor JSON data (notstring) — properties, properties_last_updated_at, properties_last_operation, default_columnsoptionalfor nullable fields- Stay at v1 for additive changes; bump to v2 only for breaking changes
- Never reuse field numbers after deletion
- Request messages:
<RpcName>Request - Response messages:
<RpcName>Response(or a shared response type likePersonsResponseif appropriate) - Include
ReadOptions read_optionsfield on read requests that need consistency control
ReadOptions and consistency
// Already defined in common.proto — import it, don't redefine
import "personinsights/types/v1/common.proto";
message YourReadRequest {
int64 team_id = 1;
// ... other fields ...
ReadOptions read_options = N; // last field
}
ReadOptions contains a ConsistencyLevel enum:
EVENTUAL(default) — queries the replica poolSTRONG— queries the primary pool (only meaningful for PersonData reads)
For NonPersonData, the replica handles consistency internally — the router always sends to replica regardless of consistency level.
File organization
| Proto file | Purpose |
|---|---|
proto/personinsights/types/v1/person.proto |
Person, DistinctId messages and all person-related request/response types |
proto/personinsights/types/v1/group.proto |
Group, GroupTypeMapping messages and all group-related request/response types |
proto/personinsights/types/v1/cohort.proto |
CohortMembership messages and cohort-related request/response types |
proto/personinsights/types/v1/feature_flag.proto |
HashKeyOverride messages and feature flag request/response types |
proto/personinsights/types/v1/common.proto |
ReadOptions, ConsistencyLevel, TeamDistinctId — shared across domains |
proto/personinsights/service/v1/service.proto |
PersonService — public API (what clients call) |
proto/personinsights/replica/v1/replica.proto |
PersonReplica — internal API (what the router calls) |
proto/personinsights/leader/v1/leader.proto |
PersonLeader — internal write API for person data |
Worked example: adding GetPersonCount
1. Add messages to types/v1/person.proto
message GetPersonCountRequest {
int64 team_id = 1;
ReadOptions read_options = 2;
}
message GetPersonCountResponse {
int64 count = 1;
}
2. Add RPC to service/v1/service.proto
service PersonService {
// ... existing RPCs ...
rpc GetPersonCount(personinsights.types.v1.GetPersonCountRequest) returns (personinsights.types.v1.GetPersonCountResponse);
}
3. Add RPC to replica/v1/replica.proto
service PersonReplica {
// ... existing RPCs ...
rpc GetPersonCount(personinsights.types.v1.GetPersonCountRequest) returns (personinsights.types.v1.GetPersonCountResponse);
}
The RPC signature must be identical in both service and replica protos.
4. Routing decision
This is a PersonData read → routed to replica for EVENTUAL, leader for STRONG. In the router, use:
let route = route_request(
DataCategory::PersonData,
OperationType::Read,
get_consistency(&request.read_options),
)?;
5. Python client wrapper (in client.py)
def get_person_count(self, request: GetPersonCountRequest) -> GetPersonCountResponse:
return self._stub.GetPersonCount(request, timeout=self._timeout)
6. Fake client method (in fake_client.py)
def get_person_count(self, request: Any) -> Any:
team_id = request.team_id
count = sum(1 for (tid, _) in self._persons_by_id if tid == team_id)
call = _Call("get_person_count", request)
resp = person_pb2.GetPersonCountResponse(count=count) # or however the response is built
call.response = resp
self.calls.append(call)
return resp