Before writing a lot of libraries, it is worth spending some time working out how the attributes for a library are structured, so all the libraries in the system are consistent. Here are some guidelines to use during your query library design phase:
I would also suggest coming up with a consistent naming convention before developing lots of libraries. In particular, you need a convention for the names of the library arguments, library definition, implementing module, library implementation and the attribute that wraps the use of the library. (E.g., something like IXArgs, Xinterface, DoX, Xlibrary, and X()).
This mechanism (example shown below) provides documentation for the parameters required by a service. It means the code inside the implementation will access them as args.xxx or options.xxx, so it will be clear when parameters are being accessed. It also makes some of the following suggestions simpler.
Making the LIBRARY function call a functional attribute (example also shown below) means you can easily modify all uses of a library if you are developing a new version. Similarly you can easily switch to use an internal library instead by changing just the one line of code.
Use a MODULE structure (without the LIBRARY option) that implements the library's INTERFACE, and a separate MODULE derived from the first to implement the LIBRARY using that service module. By hiding the LIBRARY and using a separate MODULE implementation you can easily remove the library all together. Also, using a separate implementation from the library definitions means you can easily generate multiple variants of the same library from the same definition.
NamesRec := RECORD INTEGER1 NameID; STRING20 FName; STRING20 LName; END; NamesTable := DATASET([ {1,'Doc','Holliday'}, {2,'Liz','Taylor'}, {3,'Mr','Nobody'}, {4,'Anywhere','but here'}], NamesRec); //define an INTERFACE for the passed parameters IFilterArgs := INTERFACE EXPORT DATASET(namesRec) ds; EXPORT STRING search; END; //then define an INTERFACE for the query library FilterLibIface2(IFilterArgs args) := INTERFACE EXPORT DATASET(namesRec) matches; EXPORT DATASET(namesRec) others; END; //implement the INTERFACE FilterDsLib(IFilterArgs args) := MODULE EXPORT matches := args.ds(Lname = args.search); EXPORT others := args.ds(Lname != args.search); END; //then derive that MODULE to implement the LIBRARY FilterDsLib2(IFilterArgs args) := MODULE(FilterDsLib(args)),LIBRARY(FilterLibIface2) END; //make the LIBRARY call a function FilterDs(IFilterArgs args) := LIBRARY(INTERNAL(FilterDsLib2),FilterLibIface2(args)); //easily modified to eliminate the LIBRARY, if desired // FilterDs(IFilterArgs args) := FilterDsLib2(args)); //define the parameters to pass as the interface SearchArgs := MODULE(IFilterArgs) EXPORT DATASET(namesRec) ds := NamesTable; EXPORT STRING search := 'Holliday'; END; //use the LIBRARY, passing the parameters OUTPUT(FilterDs(SearchArgs).matches); OUTPUT(FilterDs(SearchArgs).others);