ApexTools
Write the scaffolding once, not once per project.Trigger branching, and the callout mock.
Hand-written Trigger.isAfter branching and an HttpCalloutMock implementation class are not things you should be writing once per project. ApexTools is the foundation-layer utility collection that takes them off your hands.
public with sharing class TriggerOppHandler extends TriggerHandler { protected override void afterUpdate( Map<Id, SObject> newMap, Map<Id, SObject> oldMap) { // one line to keep only the changed records Set<Id> needIds = this.getUpdateRecordIdsWithChangedFields( new List<SObjectField>{ Opportunity.StageName }); (new RegenerateCollection(needIds)).invoke(); }}// No Trigger.isAfter. No new/old comparison.Override only the hooks you need, out of the seven. The moment you write extends TriggerHandler, deciding the trigger context stops being your job.
What is in the box
A utility collection for the foundation layer. What is in it today isthe two things every project ended up rewriting. More will follow whenever something of the same nature turns up.
The TriggerHandler base class
It holds the seven hooks (beforeInsert throughafterUndelete, plusandFinally) asprotected virtual. Override only what you need, andhand-written `Trigger.isAfter && Trigger.isInsert` branching disappears. Four helpers that return "only the records where these fields changed" come with it.
IHttpRequestHandler
It makes callouts replaceable through DI:HttpRequestHandler in production,MockHttpRequestHandler in tests. Responses are assembled purely by declaring MockResponse, soeven an ordered sequence needs no `HttpCalloutMock` implementation class.
Two features with nothing in common.Except where they came from.
Trigger branching and HTTP mocking have nothing to do with each other as features. What they share is where they came from: the platform never shipped them, sosomebody rewrote them on every project. And each rewrite came out a little different, so the same review discussion happened again and again.
ApexTools is the place where that gets written once and settled. The bar for inclusion is exactly that origin (the platform did not ship it, and you rewrote it every time), somore will be added whenever something clears it. Two of them are in so far.
How it reads
For each of the two pillars, here is the code you kept rewriting, next to what replaces it.
public void execute() { if (Trigger.isAfter && Trigger.isUpdate) { Set<Id> needIds = new Set<Id>(); for (Opportunity o : (List<Opportunity>) Trigger.new) { Opportunity old = (Opportunity) Trigger.oldMap.get(o.Id); if (o.StageName != old.StageName) { needIds.add(o.Id); } } (new RegenerateCollection(needIds)).invoke(); } else if (Trigger.isAfter && Trigger.isInsert) { // ... }}protected override void afterUpdate( Map<Id, SObject> newMap, Map<Id, SObject> oldMap) { Set<Id> needIds = this.getUpdateRecordIdsWithChangedFields( new List<SObjectField>{ Opportunity.StageName }); (new RegenerateCollection(needIds)).invoke();}public class MyCalloutMock implements HttpCalloutMock { private Integer callCount = 0; public HttpResponse respond(HttpRequest req) { HttpResponse res = new HttpResponse(); callCount++; if (callCount == 1) { res.setStatusCode(404); res.setBody('{"message":"not found"}'); } else if (callCount == 2) { res.setStatusCode(201); res.setBody('{"id":"abc"}'); } else { res.setStatusCode(200); res.setBody('{"id":"abc","name":"Acme"}'); } return res; }}MockHttpRequestHandler mock = new MockHttpRequestHandler(new List<MockResponse>{ MockResponse.of('GET').respond(notFound, 404), MockResponse.of('POST').respond(created, 201), MockResponse.of('GET').respond(found, 200) }); new KintoneUpsertUsecase(input, mock).invoke(); Assert.areEqual(1, mock.countByMethod('POST'));What disappears in both cases is code that manages state by hand. On the left it is the Trigger context check in one case and the callCount counter in the other. On the right both have become declarations: the hook name carries the timing, the list order carries the response sequence.
Position in Apex Stem
ApexTools is the Foundation member of the four OSS libraries that make upApex Stem. It underpins the Handler layer itself in theHandler-Usecase Architecture: where the other three work inside the Usecase, ApexTools sits outside it.
- TriggerHandler: the seven hooks and the four field-change detection helpers
- IHttpRequestHandler: script mode, label mode, and the Content-Type guard
- Handler-Usecase Architecture: the design of the Handler layer ApexTools underpins
- Layered Constructor Pattern: the base pattern behind
IHttpRequestHandler's DI design
Start with the Developer Guide
The seven TriggerHandler hooks and the two IHttpRequestHandler modes, followed through in real code.