github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/doc/hacking-state.txt (about) 1 Hacking the juju-core/state package 2 =================================== 3 4 This document remains a work in progress; it's an attempt to capture 5 the various conventions and things to bear in mind that aren't 6 necessarily written down anywhere else. 7 8 return values: ok vs err 9 ------------------------ 10 11 By convention, anything that could reasonably fail must use a separate 12 channel to communicate the failure. Broadly speaking, methods that can 13 fail in more than one way must return an error; those that can only 14 fail in one way (eg Machine.InstanceId: the value is either valid or 15 missing) are expected to return a bool for consistency's sake, even if 16 the type of the return value is such that failure can be signalled in- 17 band. 18 19 changes to entities 20 ------------------- 21 22 Entity objects reflect remote state that may change at any time, but we 23 don't want to make that too obvious. By convention, the only methods 24 that should change an entity's in-memory document are as follows: 25 26 * Refresh(), which should update every field. 27 * Methods that set fields remotely should update only those fields 28 locally. 29 30 The upshot of this is that it's not appropriate to call Refresh on any 31 argument to a state method, including receivers; if you're in a context 32 in which that would be helpful, you should clone the entity first. This 33 is simple enough that there's no Clone() method, but it would be kinda 34 nice to implement them in our Copious Free Time; I think there are 35 places outside state that would also find it useful. 36 37 care and feeding of mgo/txn 38 --------------------------- 39 40 Just about all our writes to mongodb are mediated by the mgo/txn 41 package, and using this correctly demands some care. Not all the code 42 has been written in a fully aware state, and cases in which existing 43 practice is divergent from the advice given below should be regarded 44 with some suspicion. 45 46 The txn package lets you make watchable changes to mongodb via lists of 47 operations with the following critical properties: 48 49 * transactions can apply to more than one document (this is rather 50 the point of having them). 51 * transactions will complete if every assert in the transaction 52 passes; they will not run at all if any assert fails. 53 * multi-document transactions are *not* atomic; the operations are 54 applied in the order specified by the list. 55 * operations, and hence assertions, can only be applied to documents 56 with ids that are known at the time the operation list is built; 57 this means that it takes extra work to specify a condition like 58 "no unit document newer than X exists". 59 60 The second point above deserves further discussion. Whenever you are 61 implementing a state change, you should consider the impact of the 62 following possible mongodb states on your code: 63 64 * if mongodb is already in a state consistent with the transaction 65 having already been run -- which is *always* possible -- you 66 should return immediately without error. 67 * if mongodb is in a state that indicates the transaction is not 68 valid -- eg trying to add a unit to a dying service -- you should 69 return immediately with a descriptive error. 70 71 Each of the above situations should generally be checked as part of 72 preparing a new []txn.Op, but in some cases it's convenient to trust 73 (to begin with) that the entity's in-memory state reflects reality. 74 Regardless, your job is to build a list of operations which assert 75 that: 76 77 * the transaction still needs to be applied. 78 * the transaction is actively valid. 79 * facts on which the transaction list's form depends remain true. 80 81 If you're really lucky you'll get to write a transaction in which 82 the third requirement collapses to nothing, but that's not really 83 the norm. In practice, you need to be prepared for the run to return 84 txn.ErrAborted; if this happens, you need to check for previous success 85 (and return nil) or for known cause of invalidity (and return an error 86 describing it). 87 88 If neither of these cases apply, you should assume it's an assertion 89 failure of the third kind; in that case, you should build a new 90 transaction based on more recent state and try again. If ErrAborteds 91 just keep coming, give up; there's an ErrExcessiveContention that 92 helps to describe the situation. 93 94 watching entities, and select groups thereof 95 -------------------------------------------- 96 97 The mgo/txn log enables very convenient notifications of changes to 98 particular documents and groups thereof. The state/watcher package 99 converts the txn event log into events and send them down client- 100 supplied channels for further processing; the state code itself 101 implements watchers in terms of these events. 102 103 All the internally-relevant watching code is implemented in the file 104 state/watcher.go. These constructs can be broadly divided into groups 105 as follows: 106 107 * single-document watchers: dead simple, they notify of every 108 change to a given doc. SettingsWatcher bucks the convention of 109 NotifyWatcher in that it reads whole *Settings~s to send down the 110 channel rather than just sending notifications (we probably 111 shouldn't do that, but I don't think there's time to fix it right 112 now). 113 * entity group watchers: pretty simple, very common; they notify of 114 every change to the Life field among a group of entities in the 115 same collection, with group membership determined in a wide variety 116 of ways. 117 * relation watchers: of a similar nature to entity group watchers, 118 but generating carefully-ordered events from observed changes to 119 several different collections, none of which have Life fields. 120 121 Implementation of new watchers is not necessarily a simple task, unless 122 it's a genuinely trivial refactoring (or, given lack of generics, copy- 123 and-paste job). Unless you already know exactly what you're doing, 124 please start a discussion about what you're trying to do before you 125 embark on further work in this area. 126 127 transactions and reference counts 128 --------------------------------- 129 130 As described above, it's difficult to assert things like "no units of 131 service X exist". It can be done, but not without additional work, and 132 we're generally using reference counts to enable this sort of thing. 133 134 In general, reference counts should not be stored within entities: such 135 changes are part of the operation of juju and are not important enough 136 to be reflected as a constant stream of events. We didn't figure this 137 out until recently, though, so relationDoc has a UnitCount, and 138 serviceDoc has both a UnitCount and a RelationCount. This doesn't 139 matter for the relation -- nothing watches relation docs directly -- 140 but I fear it matters quite hard for service, because every added or 141 removed unit or relation will be an unnecessary event sent to every 142 unit agent. 143 144 We'll deal with that when it's a problem rather than just a concern; 145 but new reference counts should not be thoughtlessly added to entity 146 documents, and opportunities to separate them from existing docs should 147 be considered seriously. 148 149 150 [TODO: write about globalKey and what it's good for... constraints, settings, 151 statuses, etc; occasionaly profitable use of fast prefix lookup on indexed 152 fields.] 153