For background, I am not (yet) using droids for web crawling --
rather, I use it to manage a bunch of jobs that keep external
processes running. It is easy to equate droids with crawling, but I
think that is one of many functions (though obviously the most
generally relevant)
On Jun 22, 2009, at 11:40 AM, Mingfai wrote:
hi,
I am some proposed idea for discussion. Some of them are design
principles
or concept, and some are more concrete points about design on specific
items. The points are as follows:
* - indicate items that I consider as major changes
**
1. Use of Java Package
- do not use a org.apache.droids.api package. Just put the API
interface in individual package.
+0 (sure... i have not strong opinion)
- do not use *.impl unless there are 3 or more. (i don't really
have
strong opinion at this point, just don't like to have some impl
package with
just one or two class(es))
+0
2. General coding practice/standard
- use protected instead of private by default. This will allow
users
to extend and replace any of our class easier.
- do not use final for LinkTask/Link. I have a use case that I
want to
extend Link/LinkTask as a JPA Entity Bean. And JPA doesn't
allow a class
without default constructor, i.e. we can't final the URL field.
For the
classes that are not expected to be subclassed by users, it's
ok to use
final.
- Use Java standard interface rather than introducing our own
interface unless there are obvious value-added. e.g. replace
TaskQueue with
java.util.Queue<Task>
+1
- *Use Spring for droids-core
- i.e. allow droids-core depends on Spring
- a droid needs a whole object graph to work. as a
framework, we
want different components be configurable. It's better to
rely on an IoC
framework to manage the dependency and configuration. Spring
is the most
popular IoC framework. it will also make testing much easier.
and user needs
not to change code (but to change xml) if they want to
change certain
behavior of our core classes.
- there are some special benefits of using spring, e.g. it
supports
annotation and autowiring. Take an example, I could define a
field like
"@Autowired Collection<Filter> filters", and when I add a new
filter, then,
i create 2 classes like "@Component public class XXXFilter",
and in runtime,
the filters field will be injected with a collection of the 2
classes. It
makes development and configuration real simple. (and there
are also ways to
change the autowiring behavior)
- Spring facilitates the use of http remoting. And it is
easy to
replace implementation class to do remoting or other
interception.
Does the *core* really need access to the whole object graph? I
totally agree that most specific implementations will need broader
access.
I think droids power will come from its flexibility / simplicity.
Ideally the *core* will have as few dependencies as possible.
I agree that sub-project/package that focuses on web crawling could
depend on spring.
3. Specific concept/component. Some of the points in this
section
are just my comment to the concept, but not any proposal for action.
- Droids/Crawler
- Our top level concept. We only use Droid but not Crawler.
I use
the generic term Crawler in this message.
Ya -- the term "droid" was intentionally chosen so that it represents
the larger concept of a robot doing something. Crawling is one
instance of what it may do. (again, likely the most broadly used)
- Link, LinkTask, Task
- Task is a valid concept. A task is the unit that work by the
worker.
A link refer to the link only. I have no objection to this
concept. But in
implementation, it seems there is no much need to implement
the Task
concept. (naming an interface as "nextTask" is ok but it
seems no need to
have a class or interface called "Task")
- a crawler works with links.and we don't normally non-link
related
task that goes beyond the scope of a crawler framework.
- See the Link-centric design bullet for more info about link
- Fetcher
- we do not use the concept of Fetcher now. I suppose it is
because
Droids is designed to do more than web crawling and non-web
resource is not
"fetched"? In Droids 0.01, Protocol basically represent the
Fetcher (or at
least, Protocol+Worker)
+1 -- I think a Fetcher concept is a good idea. It should also be
independent of the task interface. When thinking about the fetcher,
it may be good to consider VFS (http://commons.apache.org/vfs/) as a
first class implementation.
- I strongly think we should use the term and concept of
Fetcher
because it is a common terminology in crawler. Using common
terms and
language makes our design more intuitive.
- Parser, Handler, Processor, Extractor etc. these are terms
that
share very similar meaning. No matter how we use them, we need
to give a
strict definition, e.g. in class level JavaDoc comment. My
suggestions:
- Parser - the component that process the raw fetched Entity.
Output data is subject to implementation. One Entity will be
parsed by one
parser only.
- Extractor - the component to extract out link from entity.
Multiple extractors could be used for a parser. the primary
function is to
extract out link. user may also use it to do other extraction
or operation,
e.g. to store data in the Link, or just consume the parsed
data. A extract
depends directly to a parser. (we can't easily define a
contract between
Parser and Extractor, so let's do not attempt this.)
Extractor is a new concept. It is splitted from Parser and
diff in
a way that each link shall be parsed once, and multiple
extractor may
perform extraction or custom operation against the parsed
data. I think i
mentioned in another email before. Say when we use
NekoHtmlParser, we want
to parse just once, and maybe extractor1 is for extracting
outlink, and
extractor2 is for custom processing (such as indexing) and
both are based on
the same parsed data.
- Processor - too vague. do not use this concept, and we are
not
using it anyway.
- Handler - for event based Parser, it may use a SAX
DefaultHandler. To avoid confusion, let's not to use handler
in other
context.
agree.
"Parser" and "Extractor" make sense,
"Processor" and "Handler" are not clear to me. I know they each have
functions that can be reused by each other, but the general terms get
confusing.
- Entity
- I don't have any strong proposal and this section is just to
brainstorm some ideas. It would be good to clarify what we
want to achieve
in providing an Entity hierarchy, given that, the HC project
actually
provides all the Entity already. Our entity is kind of a
wrapper with
buffer.(and HC also have buffered entity) I guess we don't
want to depend on
HttpEntity from HC directly as Droids may touch entity beyond
HttpEntity,
e.g. File (but HC also as FileEntity...)
- Entity is the contract between Fetcher/Protocol and
Parser. For
Entity, it's unlikely the user need to subclass it. if they
need to subclass
it, they also need to implement a Fetcher/Protocol + Parser.
The value of
sub-classing Entity is not significant. I suggest we just not
design it for
subclassing.
- Currently, we have a hierarchy of ManagedContentEntity,
ContentEntity, FileContentEntity, HttpContentEntity. For a
file parser and a
http parser, they can't easily use a common Entity interface
and I suppose
the parser implementation has to cast the entity. To me,
"ManagedContentEntity" doesn't give a lot of meaning than
"Entity".
FileEntity and HttpEntity does make a different to me in
concept, but i
don't see how they could be related in implementation.
- My initial thought about the contract of parse is whether
it can
just take a InputStream. And later i find it is necessary to
have a concept
of Entity that hold information like content/mime type,
encoding/charset,
size/length etc. But diff kind of entity just may have
different attribute
and it's not easy to define a comment contract. One of the
ideas in my mind
is to use a single final Entity class that extend HashMap.
- For HttpEntity, i do prefer to have a way to retrieve the
original HC HttpEntity object. (but it is unlikely we want to
expose that in
any interface) Notice that the wrapping make it a bit more
complex in
constructing instance in unit test.
I will defer to others on the Entity discussion... I am not really
familiar with the concepts
- Worker, Task, TaskMaster
- Make worker implements Runnable, Future.(and not
RunnableFuture
for JDK5 compataibility) and we use run() as its main
interface. So it could
be use as a thread easier.
sure
- I suggest to remove the concept of Task and TaskMaster. A
Droid/Crawler could do most work of the TaskMaster. These
concepts also
confuse with Thread, ThreadFactory, Executor, that creates
many similar
concepts.
maybe -- right now, the Droid interface just handles initialization
and callbacks from the TaskMaster. It seems like that is a
substantially different concept then keeping a bunch of processes
running tasks.
- if we keep TaskMaster, i suggest to make it implements
ExecutorService, and we depends on Java util/concurrency API
rather than a
TaskMaster interface.
seems good.
- Queue
- I suggest to remove the TaskQueue interface and use Queue<?
extends Link> as standard signature.
+1
4. Link-centric design
- Link, extends HashMap, will act as a main arbitary data
container, and
a vehicle that store attributes and data thoughout the whole
lifecycle of
fetching, parsing, and extracting.
I don't have any strong opinion here.... but I would rather see an API
where we can rely on method calls then putting stuff into a Map --
perhaps years of dealing with request.getAttribute() has turned me
sour on this model.
- if we do it extremely, all data can be stored as in the Link
and all
interface could just use a single Link argument, e.g.
parse(Link),
extract(Link). For sure it is not a good idea. So i make every
interface to
include the Link argument as well as key component. I found the
extreme
usage is good in remote web service api, but not good in Java
API.
- All components to be generic as <? extends Link>, user may use
another Link implementation for the whole Droid operation. an
example is a
WeightedLink.
- For a Link, only the URL is mandatory. A ID is needed for
implementing an in-memory set/hashtable to reject duplicated
Link quickly. I
suggest to make Link a class so people could create a link with
new Link("
http://www.apache.org") easily, just like creating URL or URI.
5. Non-thread safe interface and fluent API
- take an example, insteaad of "Parse parse()", i suggest the
parser to
store the parsed data inside itself, and we provide a reset()
method to
clear the data for re-use. This design has pro and con.
- one of main pro is, we could simplify the model by omitting a
Parse
class that is mainly for holding arbitrary data. And we also
can't easily
define the return type of an interface. Take Fetcher as an
example, a
fetcher typically contain a Request and Response object. Should
we have
fetch() to return a FetchedData that has request, response, and
entity? it's
just a bit complex.
- I hope no one against Fluent API :-). with fluent api, it's
like
"public Fetcher fetch()". And I don't always use Fluent Api,
only use when
it is good and the api call may be chained. e.g.
parser.parse().getDate()
6. Factory and LinkMatcher design
- For worker, fetcher and parser, they are provided by users as a
Factory.
- For FetcherFactory and ParserFactory, new instance are
created with
a newXXX(Link link)
- So, depends on the Link, the Factory will provide diff
components.
e.g. for http link, it's a HttpFetcher, for File, it's a file
fetcher (not
impl.) for parer, it consults the content type.
- Every component implemnets a LinkMatcher interface that
checks if a
Fecher/Parser/Extractor supports a particular link. This is
primary for
automatic component registration without a need to explicitly
providing a
mapping upfront. e.g. there might be a PNG parser that checks the
"contentType" attribute of the Link. The parser implemented the
matches()
method. so we don't need to maintain a mapping hashmap between
contentType
and parser. The link matching may be complex so it's hard to use
a mapping
hashmap anyway. together with the filter framework, any
attribute could be
prepared by a filter first, so the factory could always rely on
the matcher
interface to find the correct parser/fetcher.
no real opinion -- everything sounds reasonable.
7. *Filter Framework
- This is a significant new concept. I propose to have a filter
framework that works as a chain for intercepting the works of
every main
component. There are a main lifecycle filter that is named
Filter, and also
individual component filters such as FetchFilter and
ParseFilter. Lifecycle
filter is called by a Worker. Some works may not support it,
e.g. my
WebServiceWorker that call GAE service do the whole batch of
fetch->parser->extract in one go, so there is no local filter.
Normal worker
shall call every filter after every operation. If the filter
return null, it
stop processing the link
- Filter
- When we have a confirmed lifecycle, e.g. poll a link from
queue
-> fetch entity -> parse entity -> extract outlinks, then we
have a filter
that allow inteception in between every stage. e.g.
public Link polled(Link link)
public Fetcher fetched(Link link, Fetcher fetcher)
- any filter may influence the flow by changing the component
object like Fetcher/Parser, or they may return null and the
Worker/TaskMaster shall stop the process for that link.
- e.g. Duplicated Link handling could be done as a Filter. a
singleton NoRepeatFilter stores a Set of Link ID, and when
any link is
extracted, it is check against the set and dupliated link
will be removed.
- It offers a lot of potentially such as providing runtime
statistics.
- Component filter
- e.g. FetchFilter, public void preFetch(Link, Fetcher),
postFetch(Link,Fetcher)
- component filter is expected to alter fetching behavior.
e.g. for
preFetch for a http fetcher, the http request shall be
available already,
and the preFetch could as the fetcher to the concrete class,
and modify the
content of the HttpRequest before it is executed. e.g. to
append http header
/ cookie depends on any attribute in the Link.
- The global / lifecycle filter do filter after every
component
operations. they are designed for different purpose.
sounds good
8. Removed concepts based on the above proposal
- LinkTask, Task, just keep Link
- TaskMaster - with some refactor to assign responsibility to
Droids
and Worker, the TaskMaster doesn't do too many things, and I
suggest to
remove this concept.
- TaskQueue - just use Queue<Link>
- TaskQueueWithHistory - this is eliminated by the filter
framework.
See the next section.
- TaskValidator - eliminate by the filter framework / implement
as a
Filter
- URL Filter - could be implemented as a Filter
- Parse(Parse) - merged into Parser, I think "Parse" is a vague
concept and we would rather to have a Map return from than have
a Parse
class
ya.
any comment?
In general sounds good. As for flushing out large changes like this
-- i think we should discuss it a bit more to make sure everyone is on
the same page. Then it probably makes sense to start a personal
sandbox:
http://svn.apache.org/repos/asf/incubator/droids/sandbox/mingfa
where we can see some things in action and then look at migrating
things together.
ryan