If the association will be static, i.e. a and b won't change, then you could set p at "construction" time:
NewThing[a_, b_] := <|"a" -> a, "b" -> b, "p" -> a + b|>
Sounds like you've already found a solution, but in case you want to consider another solution, you might try a wrapper.
AugmentedAssociation[data_Association]["p"] := data["a"] + data["b"];
AugmentedAssociation[data_Association][k_] := data[k]
Which can be used like this:
MyThing = AugmentedAssociation[<|"a" -> 2, "b" -> 3|>];
{MyThing["a"], MyThing["b"], MyThing["p"]}
(* {2, 3, 5} *)
Or a different type of wrapper where you can provide the functions in the data:
SpecialAssociation[data_Association][k_] := If[Function === Head[data[k]], data[k][data], data[k]];
MyThing = SpecialAssociation[<|"a" -> 2, "b" -> 3, "p" -> (#a + #b &)|>];
{MyThing["a"], MyThing["b"], MyThing["p"]}
(* {2, 3, 5} *)
What would make this reallly slick is if you defined these wrappers to work with the other Association-related functions (e.g. Lookup), which you could do with TagSetDelayed.